[
  {
    "path": ".gitattributes",
    "content": "# https://github.com/alexkaratarakis/gitattributes\n# Handle line endings automatically for files detected as text\n# and leave all files detected as binary untouched.\n* text=auto\n\n#\n# The above will handle all files NOT found below\n#\n# These files are text and should be normalized (Convert crlf => lf)\n*.css           text\n*.df            text\n*.htm           text\n*.html          text\n*.java          text\n*.js            text\n*.json          text\n*.jsp           text\n*.jspf          text\n*.jspx          text\n*.properties    text\n*.sh            text\n*.tld           text\n*.txt           text\n*.tag           text\n*.tagx          text\n*.xml           text\n*.yml           text\n\n# These files are binary and should be left untouched\n# (binary is a macro for -text -diff)\n*.class         binary\n*.dll           binary\n*.ear           binary\n*.gif           binary\n*.ico           binary\n*.jar           binary\n*.jpg           binary\n*.jpeg          binary\n*.png           binary\n*.so            binary\n*.war           binary"
  },
  {
    "path": ".gitignore",
    "content": "target/\n!.mvn/wrapper/maven-wrapper.jar\n!**/src/main/**\n!**/src/test/**\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n.sts4-cache\n\n### IntelliJ IDEA ###\n.idea\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\nnbproject/private/\nnbbuild/\ndist/\nnbdist/\n.nb-gradle/\nbuild/\n\n### VS Code ###\n.vscode/\n.gradle/\n\nbin/\n\n.DS_Store\nbuild/\n.apt_generated_tests/\n\n\n# dependencies\nnode_modules\nnpm-debug.log*\nyarn-error.log\n\n\n# umi\n.umi\n.umi-production\n\n#dist\ndist\n.hbuilderx\nunpackage\n\n.env"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [2022] [feiyu-rs]\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       http://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."
  },
  {
    "path": "README.md",
    "content": "# DEPRECATED \n\nThis is no longer supported, please consider using [https://github.com/getmoneynote/moneynote-api](https://github.com/getmoneynote/moneynote-api) instead.\n"
  },
  {
    "path": "bookkeeping-user-api/.mvn/wrapper/MavenWrapperDownloader.java",
    "content": "/*\n * Copyright 2007-present 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 */\nimport java.net.*;\nimport java.io.*;\nimport java.nio.channels.*;\nimport java.util.Properties;\n\npublic class MavenWrapperDownloader {\n\n    private static final String WRAPPER_VERSION = \"0.5.6\";\n    /**\n     * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.\n     */\n    private static final String DEFAULT_DOWNLOAD_URL = \"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/\"\n        + WRAPPER_VERSION + \"/maven-wrapper-\" + WRAPPER_VERSION + \".jar\";\n\n    /**\n     * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to\n     * use instead of the default one.\n     */\n    private static final String MAVEN_WRAPPER_PROPERTIES_PATH =\n            \".mvn/wrapper/maven-wrapper.properties\";\n\n    /**\n     * Path where the maven-wrapper.jar will be saved to.\n     */\n    private static final String MAVEN_WRAPPER_JAR_PATH =\n            \".mvn/wrapper/maven-wrapper.jar\";\n\n    /**\n     * Name of the property which should be used to override the default download url for the wrapper.\n     */\n    private static final String PROPERTY_NAME_WRAPPER_URL = \"wrapperUrl\";\n\n    public static void main(String args[]) {\n        System.out.println(\"- Downloader started\");\n        File baseDirectory = new File(args[0]);\n        System.out.println(\"- Using base directory: \" + baseDirectory.getAbsolutePath());\n\n        // If the maven-wrapper.properties exists, read it and check if it contains a custom\n        // wrapperUrl parameter.\n        File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);\n        String url = DEFAULT_DOWNLOAD_URL;\n        if(mavenWrapperPropertyFile.exists()) {\n            FileInputStream mavenWrapperPropertyFileInputStream = null;\n            try {\n                mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);\n                Properties mavenWrapperProperties = new Properties();\n                mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);\n                url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);\n            } catch (IOException e) {\n                System.out.println(\"- ERROR loading '\" + MAVEN_WRAPPER_PROPERTIES_PATH + \"'\");\n            } finally {\n                try {\n                    if(mavenWrapperPropertyFileInputStream != null) {\n                        mavenWrapperPropertyFileInputStream.close();\n                    }\n                } catch (IOException e) {\n                    // Ignore ...\n                }\n            }\n        }\n        System.out.println(\"- Downloading from: \" + url);\n\n        File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);\n        if(!outputFile.getParentFile().exists()) {\n            if(!outputFile.getParentFile().mkdirs()) {\n                System.out.println(\n                        \"- ERROR creating output directory '\" + outputFile.getParentFile().getAbsolutePath() + \"'\");\n            }\n        }\n        System.out.println(\"- Downloading to: \" + outputFile.getAbsolutePath());\n        try {\n            downloadFileFromURL(url, outputFile);\n            System.out.println(\"Done\");\n            System.exit(0);\n        } catch (Throwable e) {\n            System.out.println(\"- Error downloading\");\n            e.printStackTrace();\n            System.exit(1);\n        }\n    }\n\n    private static void downloadFileFromURL(String urlString, File destination) throws Exception {\n        if (System.getenv(\"MVNW_USERNAME\") != null && System.getenv(\"MVNW_PASSWORD\") != null) {\n            String username = System.getenv(\"MVNW_USERNAME\");\n            char[] password = System.getenv(\"MVNW_PASSWORD\").toCharArray();\n            Authenticator.setDefault(new Authenticator() {\n                @Override\n                protected PasswordAuthentication getPasswordAuthentication() {\n                    return new PasswordAuthentication(username, password);\n                }\n            });\n        }\n        URL website = new URL(urlString);\n        ReadableByteChannel rbc;\n        rbc = Channels.newChannel(website.openStream());\n        FileOutputStream fos = new FileOutputStream(destination);\n        fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);\n        fos.close();\n        rbc.close();\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/.mvn/wrapper/maven-wrapper.properties",
    "content": "distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\nwrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\n"
  },
  {
    "path": "bookkeeping-user-api/Dockerfile",
    "content": "#FROM openjdk:11-slim\n#COPY target/*.jar app.jar\n#ENTRYPOINT [\"java\",\"-jar\",\"/app.jar\"]\n\nFROM openjdk:11-slim as build\nWORKDIR /workspace/app\nCOPY mvnw .\nCOPY .mvn .mvn\nCOPY pom.xml .\nCOPY src src\nRUN ./mvnw -B -DskipTests clean package\n\nFROM openjdk:11-slim\nVOLUME /tmp\nCOPY --from=build /workspace/app/target/*.jar app.jar\nENTRYPOINT [\"java\", \"-jar\", \"/app.jar\"]"
  },
  {
    "path": "bookkeeping-user-api/mvnw",
    "content": "#!/bin/sh\n# ----------------------------------------------------------------------------\n# Licensed to the Apache Software Foundation (ASF) under one\n# or more contributor license agreements.  See the NOTICE file\n# distributed with this work for additional information\n# regarding copyright ownership.  The ASF licenses this file\n# to you under the Apache License, Version 2.0 (the\n# \"License\"); you may not use this file except in compliance\n# with the License.  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,\n# software distributed under the License is distributed on an\n# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n# KIND, either express or implied.  See the License for the\n# specific language governing permissions and limitations\n# under the License.\n# ----------------------------------------------------------------------------\n\n# ----------------------------------------------------------------------------\n# Maven Start Up Batch script\n#\n# Required ENV vars:\n# ------------------\n#   JAVA_HOME - location of a JDK home dir\n#\n# Optional ENV vars\n# -----------------\n#   M2_HOME - location of maven2's installed home dir\n#   MAVEN_OPTS - parameters passed to the Java VM when running Maven\n#     e.g. to debug Maven itself, use\n#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n# ----------------------------------------------------------------------------\n\nif [ -z \"$MAVEN_SKIP_RC\" ] ; then\n\n  if [ -f /etc/mavenrc ] ; then\n    . /etc/mavenrc\n  fi\n\n  if [ -f \"$HOME/.mavenrc\" ] ; then\n    . \"$HOME/.mavenrc\"\n  fi\n\nfi\n\n# OS specific support.  $var _must_ be set to either true or false.\ncygwin=false;\ndarwin=false;\nmingw=false\ncase \"`uname`\" in\n  CYGWIN*) cygwin=true ;;\n  MINGW*) mingw=true;;\n  Darwin*) darwin=true\n    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home\n    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html\n    if [ -z \"$JAVA_HOME\" ]; then\n      if [ -x \"/usr/libexec/java_home\" ]; then\n        export JAVA_HOME=\"`/usr/libexec/java_home`\"\n      else\n        export JAVA_HOME=\"/Library/Java/Home\"\n      fi\n    fi\n    ;;\nesac\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  if [ -r /etc/gentoo-release ] ; then\n    JAVA_HOME=`java-config --jre-home`\n  fi\nfi\n\nif [ -z \"$M2_HOME\" ] ; then\n  ## resolve links - $0 may be a link to maven's home\n  PRG=\"$0\"\n\n  # need this for relative symlinks\n  while [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n      PRG=\"$link\"\n    else\n      PRG=\"`dirname \"$PRG\"`/$link\"\n    fi\n  done\n\n  saveddir=`pwd`\n\n  M2_HOME=`dirname \"$PRG\"`/..\n\n  # make it fully qualified\n  M2_HOME=`cd \"$M2_HOME\" && pwd`\n\n  cd \"$saveddir\"\n  # echo Using m2 at $M2_HOME\nfi\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched\nif $cygwin ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --unix \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --unix \"$CLASSPATH\"`\nfi\n\n# For Mingw, ensure paths are in UNIX format before anything is touched\nif $mingw ; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=\"`(cd \"$M2_HOME\"; pwd)`\"\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=\"`(cd \"$JAVA_HOME\"; pwd)`\"\nfi\n\nif [ -z \"$JAVA_HOME\" ]; then\n  javaExecutable=\"`which javac`\"\n  if [ -n \"$javaExecutable\" ] && ! [ \"`expr \\\"$javaExecutable\\\" : '\\([^ ]*\\)'`\" = \"no\" ]; then\n    # readlink(1) is not available as standard on Solaris 10.\n    readLink=`which readlink`\n    if [ ! `expr \"$readLink\" : '\\([^ ]*\\)'` = \"no\" ]; then\n      if $darwin ; then\n        javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n        javaExecutable=\"`cd \\\"$javaHome\\\" && pwd -P`/javac\"\n      else\n        javaExecutable=\"`readlink -f \\\"$javaExecutable\\\"`\"\n      fi\n      javaHome=\"`dirname \\\"$javaExecutable\\\"`\"\n      javaHome=`expr \"$javaHome\" : '\\(.*\\)/bin'`\n      JAVA_HOME=\"$javaHome\"\n      export JAVA_HOME\n    fi\n  fi\nfi\n\nif [ -z \"$JAVACMD\" ] ; then\n  if [ -n \"$JAVA_HOME\"  ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n      # IBM's JDK on AIX uses strange locations for the executables\n      JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n      JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n  else\n    JAVACMD=\"`which java`\"\n  fi\nfi\n\nif [ ! -x \"$JAVACMD\" ] ; then\n  echo \"Error: JAVA_HOME is not defined correctly.\" >&2\n  echo \"  We cannot execute $JAVACMD\" >&2\n  exit 1\nfi\n\nif [ -z \"$JAVA_HOME\" ] ; then\n  echo \"Warning: JAVA_HOME environment variable is not set.\"\nfi\n\nCLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher\n\n# traverses directory structure from process work directory to filesystem root\n# first directory with .mvn subdirectory is considered project base directory\nfind_maven_basedir() {\n\n  if [ -z \"$1\" ]\n  then\n    echo \"Path not specified to find_maven_basedir\"\n    return 1\n  fi\n\n  basedir=\"$1\"\n  wdir=\"$1\"\n  while [ \"$wdir\" != '/' ] ; do\n    if [ -d \"$wdir\"/.mvn ] ; then\n      basedir=$wdir\n      break\n    fi\n    # workaround for JBEAP-8937 (on Solaris 10/Sparc)\n    if [ -d \"${wdir}\" ]; then\n      wdir=`cd \"$wdir/..\"; pwd`\n    fi\n    # end of workaround\n  done\n  echo \"${basedir}\"\n}\n\n# concatenates all lines of a file\nconcat_lines() {\n  if [ -f \"$1\" ]; then\n    echo \"$(tr -s '\\n' ' ' < \"$1\")\"\n  fi\n}\n\nBASE_DIR=`find_maven_basedir \"$(pwd)\"`\nif [ -z \"$BASE_DIR\" ]; then\n  exit 1;\nfi\n\n##########################################################################################\n# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n# This allows using the maven wrapper in projects that prohibit checking in binary data.\n##########################################################################################\nif [ -r \"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\" ]; then\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Found .mvn/wrapper/maven-wrapper.jar\"\n    fi\nelse\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ...\"\n    fi\n    if [ -n \"$MVNW_REPOURL\" ]; then\n      jarUrl=\"$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    else\n      jarUrl=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    fi\n    while IFS=\"=\" read key value; do\n      case \"$key\" in (wrapperUrl) jarUrl=\"$value\"; break ;;\n      esac\n    done < \"$BASE_DIR/.mvn/wrapper/maven-wrapper.properties\"\n    if [ \"$MVNW_VERBOSE\" = true ]; then\n      echo \"Downloading from: $jarUrl\"\n    fi\n    wrapperJarPath=\"$BASE_DIR/.mvn/wrapper/maven-wrapper.jar\"\n    if $cygwin; then\n      wrapperJarPath=`cygpath --path --windows \"$wrapperJarPath\"`\n    fi\n\n    if command -v wget > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found wget ... using wget\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            wget \"$jarUrl\" -O \"$wrapperJarPath\"\n        else\n            wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD \"$jarUrl\" -O \"$wrapperJarPath\"\n        fi\n    elif command -v curl > /dev/null; then\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Found curl ... using curl\"\n        fi\n        if [ -z \"$MVNW_USERNAME\" ] || [ -z \"$MVNW_PASSWORD\" ]; then\n            curl -o \"$wrapperJarPath\" \"$jarUrl\" -f\n        else\n            curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o \"$wrapperJarPath\" \"$jarUrl\" -f\n        fi\n\n    else\n        if [ \"$MVNW_VERBOSE\" = true ]; then\n          echo \"Falling back to using Java to download\"\n        fi\n        javaClass=\"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java\"\n        # For Cygwin, switch paths to Windows format before running javac\n        if $cygwin; then\n          javaClass=`cygpath --path --windows \"$javaClass\"`\n        fi\n        if [ -e \"$javaClass\" ]; then\n            if [ ! -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Compiling MavenWrapperDownloader.java ...\"\n                fi\n                # Compiling the Java class\n                (\"$JAVA_HOME/bin/javac\" \"$javaClass\")\n            fi\n            if [ -e \"$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class\" ]; then\n                # Running the downloader\n                if [ \"$MVNW_VERBOSE\" = true ]; then\n                  echo \" - Running MavenWrapperDownloader.java ...\"\n                fi\n                (\"$JAVA_HOME/bin/java\" -cp .mvn/wrapper MavenWrapperDownloader \"$MAVEN_PROJECTBASEDIR\")\n            fi\n        fi\n    fi\nfi\n##########################################################################################\n# End of extension\n##########################################################################################\n\nexport MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-\"$BASE_DIR\"}\nif [ \"$MVNW_VERBOSE\" = true ]; then\n  echo $MAVEN_PROJECTBASEDIR\nfi\nMAVEN_OPTS=\"$(concat_lines \"$MAVEN_PROJECTBASEDIR/.mvn/jvm.config\") $MAVEN_OPTS\"\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin; then\n  [ -n \"$M2_HOME\" ] &&\n    M2_HOME=`cygpath --path --windows \"$M2_HOME\"`\n  [ -n \"$JAVA_HOME\" ] &&\n    JAVA_HOME=`cygpath --path --windows \"$JAVA_HOME\"`\n  [ -n \"$CLASSPATH\" ] &&\n    CLASSPATH=`cygpath --path --windows \"$CLASSPATH\"`\n  [ -n \"$MAVEN_PROJECTBASEDIR\" ] &&\n    MAVEN_PROJECTBASEDIR=`cygpath --path --windows \"$MAVEN_PROJECTBASEDIR\"`\nfi\n\n# Provide a \"standardized\" way to retrieve the CLI args that will\n# work with both Windows and non-Windows executions.\nMAVEN_CMD_LINE_ARGS=\"$MAVEN_CONFIG $@\"\nexport MAVEN_CMD_LINE_ARGS\n\nWRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nexec \"$JAVACMD\" \\\n  $MAVEN_OPTS \\\n  -classpath \"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar\" \\\n  \"-Dmaven.home=${M2_HOME}\" \"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}\" \\\n  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG \"$@\"\n"
  },
  {
    "path": "bookkeeping-user-api/mvnw.cmd",
    "content": "@REM ----------------------------------------------------------------------------\n@REM Licensed to the Apache Software Foundation (ASF) under one\n@REM or more contributor license agreements.  See the NOTICE file\n@REM distributed with this work for additional information\n@REM regarding copyright ownership.  The ASF licenses this file\n@REM to you under the Apache License, Version 2.0 (the\n@REM \"License\"); you may not use this file except in compliance\n@REM with the License.  You may obtain a copy of the License at\n@REM\n@REM    https://www.apache.org/licenses/LICENSE-2.0\n@REM\n@REM Unless required by applicable law or agreed to in writing,\n@REM software distributed under the License is distributed on an\n@REM \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n@REM KIND, either express or implied.  See the License for the\n@REM specific language governing permissions and limitations\n@REM under the License.\n@REM ----------------------------------------------------------------------------\n\n@REM ----------------------------------------------------------------------------\n@REM Maven Start Up Batch script\n@REM\n@REM Required ENV vars:\n@REM JAVA_HOME - location of a JDK home dir\n@REM\n@REM Optional ENV vars\n@REM M2_HOME - location of maven2's installed home dir\n@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands\n@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending\n@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven\n@REM     e.g. to debug Maven itself, use\n@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\n@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files\n@REM ----------------------------------------------------------------------------\n\n@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'\n@echo off\n@REM set title of command window\ntitle %0\n@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'\n@if \"%MAVEN_BATCH_ECHO%\" == \"on\"  echo %MAVEN_BATCH_ECHO%\n\n@REM set %HOME% to equivalent of $HOME\nif \"%HOME%\" == \"\" (set \"HOME=%HOMEDRIVE%%HOMEPATH%\")\n\n@REM Execute a user defined script before this one\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPre\n@REM check for pre script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_pre.bat\" call \"%HOME%\\mavenrc_pre.bat\"\nif exist \"%HOME%\\mavenrc_pre.cmd\" call \"%HOME%\\mavenrc_pre.cmd\"\n:skipRcPre\n\n@setlocal\n\nset ERROR_CODE=0\n\n@REM To isolate internal variables from possible post scripts, we use another setlocal\n@setlocal\n\n@REM ==== START VALIDATION ====\nif not \"%JAVA_HOME%\" == \"\" goto OkJHome\n\necho.\necho Error: JAVA_HOME not found in your environment. >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n:OkJHome\nif exist \"%JAVA_HOME%\\bin\\java.exe\" goto init\n\necho.\necho Error: JAVA_HOME is set to an invalid directory. >&2\necho JAVA_HOME = \"%JAVA_HOME%\" >&2\necho Please set the JAVA_HOME variable in your environment to match the >&2\necho location of your Java installation. >&2\necho.\ngoto error\n\n@REM ==== END VALIDATION ====\n\n:init\n\n@REM Find the project base dir, i.e. the directory that contains the folder \".mvn\".\n@REM Fallback to current working directory if not found.\n\nset MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%\nIF NOT \"%MAVEN_PROJECTBASEDIR%\"==\"\" goto endDetectBaseDir\n\nset EXEC_DIR=%CD%\nset WDIR=%EXEC_DIR%\n:findBaseDir\nIF EXIST \"%WDIR%\"\\.mvn goto baseDirFound\ncd ..\nIF \"%WDIR%\"==\"%CD%\" goto baseDirNotFound\nset WDIR=%CD%\ngoto findBaseDir\n\n:baseDirFound\nset MAVEN_PROJECTBASEDIR=%WDIR%\ncd \"%EXEC_DIR%\"\ngoto endDetectBaseDir\n\n:baseDirNotFound\nset MAVEN_PROJECTBASEDIR=%EXEC_DIR%\ncd \"%EXEC_DIR%\"\n\n:endDetectBaseDir\n\nIF NOT EXIST \"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\" goto endReadAdditionalConfig\n\n@setlocal EnableExtensions EnableDelayedExpansion\nfor /F \"usebackq delims=\" %%a in (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\jvm.config\") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a\n@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%\n\n:endReadAdditionalConfig\n\nSET MAVEN_JAVA_EXE=\"%JAVA_HOME%\\bin\\java.exe\"\nset WRAPPER_JAR=\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.jar\"\nset WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain\n\nset DOWNLOAD_URL=\"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n\nFOR /F \"tokens=1,2 delims==\" %%A IN (\"%MAVEN_PROJECTBASEDIR%\\.mvn\\wrapper\\maven-wrapper.properties\") DO (\n    IF \"%%A\"==\"wrapperUrl\" SET DOWNLOAD_URL=%%B\n)\n\n@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central\n@REM This allows using the maven wrapper in projects that prohibit checking in binary data.\nif exist %WRAPPER_JAR% (\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Found %WRAPPER_JAR%\n    )\n) else (\n    if not \"%MVNW_REPOURL%\" == \"\" (\n        SET DOWNLOAD_URL=\"%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar\"\n    )\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Couldn't find %WRAPPER_JAR%, downloading it ...\n        echo Downloading from: %DOWNLOAD_URL%\n    )\n\n    powershell -Command \"&{\"^\n\t\t\"$webclient = new-object System.Net.WebClient;\"^\n\t\t\"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {\"^\n\t\t\"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');\"^\n\t\t\"}\"^\n\t\t\"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')\"^\n\t\t\"}\"\n    if \"%MVNW_VERBOSE%\" == \"true\" (\n        echo Finished downloading %WRAPPER_JAR%\n    )\n)\n@REM End of extension\n\n@REM Provide a \"standardized\" way to retrieve the CLI args that will\n@REM work with both Windows and non-Windows executions.\nset MAVEN_CMD_LINE_ARGS=%*\n\n%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% \"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%\" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*\nif ERRORLEVEL 1 goto error\ngoto end\n\n:error\nset ERROR_CODE=1\n\n:end\n@endlocal & set ERROR_CODE=%ERROR_CODE%\n\nif not \"%MAVEN_SKIP_RC%\" == \"\" goto skipRcPost\n@REM check for post script, once with legacy .bat ending and once with .cmd ending\nif exist \"%HOME%\\mavenrc_post.bat\" call \"%HOME%\\mavenrc_post.bat\"\nif exist \"%HOME%\\mavenrc_post.cmd\" call \"%HOME%\\mavenrc_post.cmd\"\n:skipRcPost\n\n@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'\nif \"%MAVEN_BATCH_PAUSE%\" == \"on\" pause\n\nif \"%MAVEN_TERMINATE_CMD%\" == \"on\" exit %ERROR_CODE%\n\nexit /B %ERROR_CODE%\n"
  },
  {
    "path": "bookkeeping-user-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\n    <parent>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-starter-parent</artifactId>\n        <version>2.6.0</version>\n        <relativePath /> <!-- lookup parent from repository -->\n    </parent>\n\n    <groupId>com.jiukuaitech</groupId>\n    <artifactId>bookkeeping-user</artifactId>\n    <version>0.1</version>\n    <name>bookkeeping-user</name>\n    <description>Bookkeeping API for User</description>\n    <packaging>jar</packaging>\n\n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <java.version>11</java.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-jpa</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>mysql</groupId>\n            <artifactId>mysql-connector-java</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.qiniu</groupId>\n            <artifactId>qiniu-java-sdk</artifactId>\n            <version>[7.7.0, 7.7.99]</version>\n        </dependency>\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <version>1.18.20</version>\n            <scope>provided</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.hibernate</groupId>\n            <artifactId>hibernate-jpamodelgen</artifactId>\n            <version>5.5.6.Final</version>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-devtools</artifactId>\n            <optional>true</optional>\n            <scope>runtime</scope>\n        </dependency>\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n            </plugin>\n            <plugin>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <compilerArguments>\n                        <processor>\n                            org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor\n                        </processor>\n                    </compilerArguments>\n                </configuration>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>"
  },
  {
    "path": "bookkeeping-user-api/restart.sh",
    "content": "#!/bin/bash\n. stop.sh\nrm -rf 1.log\n. startup.sh"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/Application.java",
    "content": "package com.jiukuaitech.bookkeeping.user;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n@SpringBootApplication\npublic class Application {\n\n\tpublic static void main(String[] args) {\n\t\tSpringApplication.run(Application.class, args);\n\t}\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/RegexTest.java",
    "content": "package com.jiukuaitech.bookkeeping.user;\n\npublic class RegexTest implements Runnable {\n\n\n    private static int i;\n\n    public synchronized static void change() {\n        for (int j = 0; j < 5000000; j++) {\n            i++;\n        }\n    }\n\n    @Override\n    public void run() {\n        synchronized (\"123\".intern()) {\n            for (int j = 0; j < 5000000; j++) {\n                i++;\n            }\n        }\n    }\n\n    public static void main(String[] args) throws Exception {\n        new Thread(new RegexTest()).start();\n        new Thread(new RegexTest()).start();\n        Thread.sleep(3000);\n        System.out.println(i);\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/Account.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport com.jiukuaitech.bookkeeping.user.base.NameNotesEnableEntity;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.validation.BalanceValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NoValidator;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Entity\n@Table(name = \"t_account\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"group_id\", \"name\"})})\n@Inheritance(strategy = InheritanceType.SINGLE_TABLE)\n@DiscriminatorColumn(name = \"type\", discriminatorType = DiscriminatorType.INTEGER, columnDefinition = \"TINYINT(1)\")\n@Getter\n@Setter\npublic class Account extends NameNotesEnableEntity {\n\n    @Column(insertable = false, updatable = false)\n    private Integer type; //1活期，2信用，3贷款，4资产\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"group_id\")\n    @NotNull\n    private Group group; // 账簿必须属于某个组\n\n    @Column(length = 32)\n    @NoValidator\n    private String no; //卡号\n\n    @Column(nullable = false) //最多9亿\n    @NotNull\n    @BalanceValidator\n    private BigDecimal balance; // 当前余额\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean include = true; //净资产是否包含\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean expenseable = true; //是否可支出\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean incomeable = true; //是否可收入\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean transferFromAble = true; //是否转账可转出\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean transferToAble = true; //是否转账可转入\n\n    @Column(nullable = false, length = 8)\n    @NotNull\n    private String currencyCode;\n\n    @Column\n    @BalanceValidator\n    private BigDecimal initialBalance; // 期初余额\n\n    public Account() { }\n\n    public Account(Integer id) {\n        super.setId(id);\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport java.math.BigDecimal;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\n\nimport com.jiukuaitech.bookkeeping.user.validation.BalanceValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NoValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class AccountAddRequest {\n\n    @NotBlank(message=\"name must not be blank\")\n    @NameValidator\n    private String name;\n\n    @NoValidator\n    private String no;\n\n    @NotNull\n    @BalanceValidator\n    private BigDecimal balance;\n\n    private Boolean include = true;\n    private Boolean transferFromAble = true;\n    private Boolean transferToAble = true;\n    private Boolean expenseable = true;\n    private Boolean incomeable = true;\n\n    @NotesValidator\n    private String notes;\n\n    @NotBlank\n    private String currencyCode;\n\n    public void copyPrimitive(Account po) {\n        po.setName(name);\n        po.setNo(no);\n        po.setBalance(balance);\n        po.setInitialBalance(balance);\n        po.setInclude(include);\n        po.setTransferFromAble(transferFromAble);\n        po.setTransferToAble(transferToAble);\n        po.setExpenseable(expenseable);\n        po.setIncomeable(incomeable);\n        po.setNotes(notes);\n        po.setCurrencyCode(currencyCode);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountAdjustBalanceNotValidException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\npublic class AccountAdjustBalanceNotValidException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport com.jiukuaitech.bookkeeping.user.adjust_balance.AdjustBalanceAddRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/accounts\")\npublic class AccountController extends BaseController {\n\n    @Resource\n    private AccountService accountService;\n\n    // 搜索流水的下拉框需要显示所有可用的account\n    @RequestMapping(method = RequestMethod.GET, value = \"/enable\")\n    public BaseResponse handleEnable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.getEnable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/expenseable\")\n    public BaseResponse handleExpenseble(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.getAllExpenseable(userSignInId));\n    }\n    \n    @RequestMapping(method = RequestMethod.GET, value = \"/incomeable\")\n    public BaseResponse handleIncomeble(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.getAllIncomeable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/transfer-from-able\")\n    public BaseResponse handleTransferFromAble(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.getAllTransferFromAble(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/transfer-to-able\")\n    public BaseResponse handleTransferToAble(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.getAllTransferToAble(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/{id}\")\n    public BaseResponse handleGet(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.get(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"/{id}/adjust-balance\")\n    public BaseResponse handleAdjustBalance(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody AdjustBalanceAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.adjustBalance(id, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.remove(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggle\")\n    public BaseResponse handleToggle(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.toggle(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggleInclude\")\n    public BaseResponse handleToggleInclude(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.toggleInclude(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggleExpenseable\")\n    public BaseResponse handleToggleExpenseable(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.toggleExpenseable(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggleIncomeable\")\n    public BaseResponse handleToggleIncomeable(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.toggleIncomeable(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggleTransferFromAble\")\n    public BaseResponse handleToggleTransferFromAble(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.toggleTransferFromAble(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggleTransferToAble\")\n    public BaseResponse handleToggleTransferToAble(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.toggleTransferToAble(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody AccountUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.update(id, request, userSignInId));\n    }\n\n}"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class AccountExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = AccountNameExistsException.class)\n//    @ResponseStatus(HttpStatus.CONFLICT)\n    @ResponseBody\n    public BaseResponse handleException(AccountNameExistsException e) {\n        return new ErrorResponse(409, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = AccountMaxCountException.class)\n    @ResponseBody\n    public BaseResponse handleException(AccountMaxCountException e) {\n        return new ErrorResponse(501, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = AccountHasTransactionException.class)\n    @ResponseBody\n    public BaseResponse handleException(AccountHasTransactionException e) {\n        return new ErrorResponse(601, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = AccountAdjustBalanceNotValidException.class)\n    @ResponseBody\n    public BaseResponse handleException(AccountAdjustBalanceNotValidException e) {\n        return new ErrorResponse(602, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = DefaultExpenseAccountException.class)\n    @ResponseBody\n    public BaseResponse handleException(DefaultExpenseAccountException e) {\n        return new ErrorResponse(603, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = DefaultIncomeAccountException.class)\n    @ResponseBody\n    public BaseResponse handleException(DefaultIncomeAccountException e) {\n        return new ErrorResponse(604, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = DefaultTransferFromAccountException.class)\n    @ResponseBody\n    public BaseResponse handleException(DefaultTransferFromAccountException e) {\n        return new ErrorResponse(605, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = DefaultTransferToAccountException.class)\n    @ResponseBody\n    public BaseResponse handleException(DefaultTransferToAccountException e) {\n        return new ErrorResponse(606, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountHasTransactionException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\npublic class AccountHasTransactionException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountMaxCountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\npublic class AccountMaxCountException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountNameExistsException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\npublic class AccountNameExistsException extends RuntimeException {\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountQueryRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class AccountQueryRequest {\n\n    private Boolean enable;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.Optional;\n\n@Repository\npublic interface AccountRepository extends BaseRepository<Account, Integer> {\n\n    Optional<Account> findOneByGroupAndName(Group group, String name);\n\n    Optional<Account> findOneByGroupAndId(Group group, Integer id);\n\n    long countByGroup(Group group);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport com.jiukuaitech.bookkeeping.user.adjust_balance.AdjustBalance;\nimport com.jiukuaitech.bookkeeping.user.adjust_balance.AdjustBalanceAddRequest;\nimport com.jiukuaitech.bookkeeping.user.adjust_balance.AdjustBalanceRepository;\nimport com.jiukuaitech.bookkeeping.user.asset_account.AssetAccount;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.checking_account.CheckingAccount;\nimport com.jiukuaitech.bookkeeping.user.credit_account.CreditAccount;\nimport com.jiukuaitech.bookkeeping.user.debt_account.DebtAccount;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.credit_account.CreditAccountAddRequest;\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.debt_account.DebtAccountAddRequest;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionRepository;\nimport com.jiukuaitech.bookkeeping.user.transfer.TransferRepository;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLog;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLogRepository;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLogService;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.StringUtils;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.time.Instant;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Service\npublic class AccountService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private AccountRepository accountRepository;\n    \n    @Resource\n    private AdjustBalanceRepository adjustBalanceRepository;\n\n    @Resource\n    private TransactionRepository transactionRepository;\n\n    @Resource\n    private TransferRepository transferRepository;\n\n    @Resource\n    private UserActionLogRepository userActionLogRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    @Resource\n    private UserActionLogService userActionLogService;\n\n    @Value(\"${account.max.count}\")\n    private Integer accountMaxCount;\n\n    public List<AccountVOForExtend> getEnable(Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<Account> entityList = accountRepository.findAll(AccountSpec.inGroupAndEnable(group));\n        return entityList.stream().map(AccountVOForExtend::fromEntity).collect(Collectors.toList());\n    }\n\n    public List<AccountVOForExtend> getAllExpenseable(Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<Account> entityList = accountRepository.findAll(AccountSpec.inGroupAndExpenseable(group));\n        return entityList.stream().map(AccountVOForExtend::fromEntity).collect(Collectors.toList());\n    }\n    \n    public List<AccountVOForExtend> getAllIncomeable(Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<Account> entityList = accountRepository.findAll(AccountSpec.inGroupAndIncomeable(group));\n        return entityList.stream().map(AccountVOForExtend::fromEntity).collect(Collectors.toList());\n    }\n\n    public List<AccountVOForExtend> getAllTransferFromAble(Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<Account> entityList = accountRepository.findAll(AccountSpec.inGroupAndTransferFromAble(group));\n        return entityList.stream().map(AccountVOForExtend::fromEntity).collect(Collectors.toList());\n    }\n    \n    public List<AccountVOForExtend> getAllTransferToAble(Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<Account> entityList = accountRepository.findAll(AccountSpec.inGroupAndTransferToAble(group));\n        return entityList.stream().map(AccountVOForExtend::fromEntity).collect(Collectors.toList());\n    }\n\n    public AccountVOForList get(Integer id, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        AccountVOForList vo = new AccountVOForList();\n        vo.setValue(po);\n        vo.setConvertedBalance(currencyService.convert(vo.getBalance(), vo.getCurrencyCode(), group.getDefaultCurrencyCode()));\n        if (po instanceof CreditAccount) {\n            CreditAccount po2 = (CreditAccount) po;\n            vo.setLimit(po2.getLimit());\n            vo.setBillDay(po2.getBillDay());\n        }\n        if (po instanceof DebtAccount) {\n            DebtAccount po2 = (DebtAccount) po;\n            vo.setApr(po2.getApr());\n            vo.setLimit(po2.getLimit());\n        }\n        if (po instanceof AssetAccount) {\n            AssetAccount po2 = (AssetAccount) po;\n            vo.setAsOfDate(po2.getAsOfDate());\n        }\n        return vo;\n    }\n\n    @Transactional\n    public AccountVOForExtend adjustBalance(Integer id, AdjustBalanceAddRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n\n        Group group = user.getDefaultGroup();\n        Account account = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        if (account.getBalance().compareTo(request.getBalance()) == 0) {\n            throw new AccountAdjustBalanceNotValidException();\n        }\n        BigDecimal adjustAmount = request.getBalance().subtract(account.getBalance());\n        account.setBalance(request.getBalance());\n        // 如果是资产账户改一下截止日期\n        if (account instanceof AssetAccount) {\n            AssetAccount assetAccount = (AssetAccount) account;\n            assetAccount.setAsOfDate(request.getCreateTime());\n        }\n        accountRepository.save(account);\n\n        AdjustBalance po = new AdjustBalance();\n        request.copyPrimitive(po);\n        po.setCreator(user);\n        po.setBook(user.getDefaultBook());\n        po.setGroup(user.getDefaultGroup());\n        po.setAmount(adjustAmount);\n        po.setAccount(account);\n        po.setStatus(1);\n\n        adjustBalanceRepository.save(po);\n        userActionLogRepository.save(new UserActionLog(user, 1, Instant.now().toEpochMilli()));\n        return AccountVOForExtend.fromEntity(po.getAccount());\n    }\n\n    // 1. 余额调整记录删除，2. 日志删除\n    @Transactional\n    public AccountVOForExtend remove(Integer id, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        // 检查关联性，有账单关联的不能删除\n        if (transactionRepository.countByAccount_id(id) > 0 || transferRepository.countByTo_id(id) > 0) {\n            throw new AccountHasTransactionException();\n        }\n        adjustBalanceRepository.deleteByAccount_Id(id);\n        accountRepository.delete(po);\n        return AccountVOForExtend.fromEntity(po);\n    }\n\n    public AccountVOForExtend toggle(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        Book book = user.getDefaultBook();\n        if (po.equals(book.getDefaultExpenseAccount())) throw new DefaultExpenseAccountException();\n        if (po.equals(book.getDefaultIncomeAccount())) throw new DefaultIncomeAccountException();\n        po.setEnable(!po.getEnable());\n        accountRepository.save(po);\n        return AccountVOForExtend.fromEntity(accountRepository.save(po));\n    }\n\n    public boolean toggleInclude(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        po.setInclude(!po.getInclude());\n        accountRepository.save(po);\n        return true;\n    }\n\n    public boolean toggleExpenseable(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        Book book = user.getDefaultBook();\n        if (po.equals(book.getDefaultExpenseAccount())) throw new DefaultExpenseAccountException();\n        po.setExpenseable(!po.getExpenseable());\n        accountRepository.save(po);\n        return true;\n    }\n\n    public boolean toggleIncomeable(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        Book book = user.getDefaultBook();\n        if (po.equals(book.getDefaultIncomeAccount())) throw new DefaultIncomeAccountException();\n        po.setIncomeable(!po.getIncomeable());\n        accountRepository.save(po);\n        return true;\n    }\n\n    public boolean toggleTransferFromAble(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        Book book = user.getDefaultBook();\n        if (po.equals(book.getDefaultTransferFromAccount())) throw new DefaultTransferFromAccountException();\n        po.setTransferFromAble(!po.getTransferFromAble());\n        accountRepository.save(po);\n        return true;\n    }\n\n    public boolean toggleTransferToAble(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        Book book = user.getDefaultBook();\n        if (po.equals(book.getDefaultTransferToAccount())) throw new DefaultTransferToAccountException();\n        po.setTransferToAble(!po.getTransferToAble());\n        accountRepository.save(po);\n        return true;\n    }\n\n    public boolean add(Integer type, AccountAddRequest request, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        if (accountRepository.countByGroup(group) >= accountMaxCount) {\n            throw new AccountMaxCountException();\n        }\n        if (accountRepository.findOneByGroupAndName(group, request.getName()).isPresent()) {\n            throw new AccountNameExistsException();\n        }\n        currencyService.checkCode(request.getCurrencyCode());\n        Account po = null;\n        switch (type) {\n            case 1:\n                po = new CheckingAccount();\n                request.copyPrimitive(po);\n                break;\n            case 2:\n                po = new CreditAccount();\n                ((CreditAccountAddRequest) request).copyPrimitive((CreditAccount) po);\n                break;\n            case 3:\n                po = new DebtAccount();\n                ((DebtAccountAddRequest) request).copyPrimitive((DebtAccount) po);\n                break;\n            case 4:\n                po = new AssetAccount();\n                request.copyPrimitive(po);\n                ((AssetAccount)po).setAsOfDate(Instant.now().toEpochMilli());\n                break;\n        }\n        po.setGroup(group);\n        accountRepository.save(po);\n        return true;\n    }\n\n    public AccountVOForExtend update(Integer id, AccountUpdateRequest request, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Account po = accountRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        if (StringUtils.hasText(request.getName())) {\n            if (!po.getName().equals(request.getName())) {\n                if (accountRepository.findOneByGroupAndName(group, request.getName()).isPresent()) {\n                    throw new AccountNameExistsException();\n                }\n            }\n        }\n        request.updatePrimitive(po);\n        accountRepository.save(po);\n        return AccountVOForExtend.fromEntity(po);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableSpec;\nimport org.springframework.data.jpa.domain.Specification;\n\npublic final class AccountSpec {\n\n    public static<T extends Account> Specification<T> includeTrue() {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Account_.include), true);\n    }\n\n    public static<T extends Account> Specification<T> expenseable() {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Account_.expenseable), true);\n    }\n\n    public static<T extends Account> Specification<T> incomeable() {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Account_.incomeable), true);\n    }\n\n    public static<T extends Account> Specification<T> transferFromAble() {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Account_.transferFromAble), true);\n    }\n\n    public static<T extends Account> Specification<T> transferToAble() {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Account_.transferToAble), true);\n    }\n\n    public static<T extends Account> Specification<T> inGroup(Group group) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Account_.group), group);\n    }\n\n    public static<T extends Account> Specification<T> inGroupAndEnable(Group group) {\n        Specification<T> specification = inGroup(group);\n        return specification.and(BookNameNotesEnableSpec.enable());\n    }\n\n    public static<T extends Account> Specification<T> inGroupAndInclude(Group group) {\n        Specification<T> specification = inGroupAndEnable(group);\n        return specification.and(includeTrue());\n    }\n\n    public static<T extends Account> Specification<T> inGroupAndExpenseable(Group group) {\n        Specification<T> specification = inGroupAndEnable(group);\n        return specification.and(expenseable());\n    }\n\n    public static<T extends Account> Specification<T> inGroupAndIncomeable(Group group) {\n        Specification<T> specification = inGroupAndEnable(group);\n        return specification.and(incomeable());\n    }\n\n    public static<T extends Account> Specification<T> inGroupAndTransferFromAble(Group group) {\n        Specification<T> specification = inGroupAndEnable(group);\n        return specification.and(transferFromAble());\n    }\n\n    public static<T extends Account> Specification<T> inGroupAndTransferToAble(Group group) {\n        Specification<T> specification = inGroupAndEnable(group);\n        return specification.and(transferToAble());\n    }\n\n    public static<T extends Account> Specification<T> buildSpecification(AccountQueryRequest request, Group group) {\n        Specification<T> specification = inGroup(group);\n        if (request.getEnable() != null) {\n            specification = specification.and(BookNameNotesEnableSpec.isEnable(request.getEnable()));\n        }\n        return specification;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountSumVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class AccountSumVO {\n\n    private BigDecimal balance;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport java.math.BigDecimal;\n\nimport com.jiukuaitech.bookkeeping.user.credit_account.CreditAccount;\nimport com.jiukuaitech.bookkeeping.user.debt_account.DebtAccount;\nimport com.jiukuaitech.bookkeeping.user.validation.*;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class AccountUpdateRequest {\n\n    @NameValidator\n    private String name;\n    @NoValidator\n    private String no;\n    private Boolean include;\n    private Boolean transferFromAble;\n    private Boolean transferToAble;\n    private Boolean expenseable;\n    private Boolean incomeable;\n    @NotesValidator\n    private String notes;\n\n    // For Credit\n    @CreditLimitValidator\n    private BigDecimal limit; // 信用额度\n    @BillDayValidator\n    private Integer billDay; // 每月多少号是账单日\n\n    @AprValidator\n    private BigDecimal apr;\n\n    public void updatePrimitive(Account po) {\n        if (name != null) po.setName(name);\n        if (no != null) po.setNo(no);\n        if (include != null) po.setInclude(include);\n        if (transferFromAble != null) po.setTransferFromAble(transferFromAble);\n        if (transferToAble != null) po.setTransferToAble(transferToAble);\n        if (expenseable != null) po.setExpenseable(expenseable);\n        if (incomeable != null) po.setIncomeable(incomeable);\n        if (notes != null) po.setNotes(notes);\n        if (po instanceof CreditAccount) {\n            if (limit != null) ((CreditAccount)po).setLimit(limit);\n            if (billDay != null) ((CreditAccount)po).setBillDay(billDay);\n        } else if (po instanceof DebtAccount) {\n            if (limit != null) ((DebtAccount)po).setLimit(limit);\n            if (apr != null) ((DebtAccount)po).setApr(apr);\n        }\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountVOForExtend.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport com.jiukuaitech.bookkeeping.user.utils.EnumUtils;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class AccountVOForExtend {\n\n    private Integer type; //1活期，2信用，3贷款，4资产\n    private Integer id;\n    private String name;\n    private String no;\n    private BigDecimal balance;\n    private BigDecimal convertedBalance;\n    private String currencyCode;\n    private Boolean enable;\n    private Boolean include;\n    private Boolean expenseable;\n    private Boolean incomeable;\n    private Boolean transferFromAble;\n    private Boolean transferToAble;\n    private BigDecimal initialBalance;\n    private String notes;\n\n    public void setValue(Account po) {\n        setType(po.getType());\n        setId(po.getId());\n        setName(po.getName());\n        setNo(po.getNo());\n        setBalance(po.getBalance());\n        setEnable(po.getEnable());\n        setInclude(po.getInclude());\n        setExpenseable(po.getExpenseable());\n        setIncomeable(po.getIncomeable());\n        setTransferFromAble(po.getTransferFromAble());\n        setTransferToAble(po.getTransferToAble());\n        setInitialBalance(po.getInitialBalance());\n        setNotes(po.getNotes());\n        setCurrencyCode(po.getCurrencyCode());\n    }\n\n    public static AccountVOForExtend fromEntity(Account po) {\n        if (po == null) return null;\n        AccountVOForExtend vo = new AccountVOForExtend();\n        vo.setValue(po);\n        return vo;\n    }\n\n    public String getTypeName() {\n        return EnumUtils.translateAccountType(type);\n    }\n\n    public String getBalanceFormatted() {\n        return String.format(\"%.2f\", balance);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/AccountVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class AccountVOForList extends AccountVOForExtend {\n\n    private BigDecimal limit;\n    private Integer billDay;\n\n    private BigDecimal apr;\n    private Long asOfDate;\n\n    public BigDecimal getRemainLimit() {\n        if (limit == null) return null;\n        return limit.add(getBalance());\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/DefaultExpenseAccountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\npublic class DefaultExpenseAccountException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/DefaultIncomeAccountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\npublic class DefaultIncomeAccountException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/DefaultTransferFromAccountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\npublic class DefaultTransferFromAccountException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/account/DefaultTransferToAccountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.account;\n\npublic class DefaultTransferToAccountException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/adjust_balance/AdjustBalance.java",
    "content": "package com.jiukuaitech.bookkeeping.user.adjust_balance;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlow;\n\nimport javax.persistence.*;\n\n@Entity\n@DiscriminatorValue(value = \"4\")\npublic class AdjustBalance extends BalanceFlow {\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/adjust_balance/AdjustBalanceAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.adjust_balance;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowAddRequest;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class AdjustBalanceAddRequest extends BalanceFlowAddRequest {\n\n    @NotNull\n    private BigDecimal balance;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/adjust_balance/AdjustBalanceController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.adjust_balance;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowUpdateRequest;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n\n@RestController\n@RequestMapping(\"/adjust-balances\")\npublic class AdjustBalanceController extends BaseController {\n\n    @Resource\n    private AdjustBalanceService adjustBalanceService;\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody BalanceFlowUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(adjustBalanceService.update(id, request, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/adjust_balance/AdjustBalanceRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.adjust_balance;\n\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface AdjustBalanceRepository extends HasBookRepository<AdjustBalance> {\n\n    void deleteByAccount_Id(Integer id);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/adjust_balance/AdjustBalanceService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.adjust_balance;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.account.AccountRepository;\nimport com.jiukuaitech.bookkeeping.user.account.AccountVOForExtend;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowUpdateRequest;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport javax.annotation.Resource;\n\n@Service\npublic class AdjustBalanceService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private AccountRepository accountRepository;\n\n    @Resource\n    private AdjustBalanceRepository adjustBalanceRepository;\n\n    @Transactional\n    public AccountVOForExtend remove(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        AdjustBalance po = adjustBalanceRepository.findOneByBookAndId(user.getDefaultBook(), id).orElseThrow(ItemNotFoundException::new);\n        // 处理退款\n        Account account = po.getAccount();\n        account.setBalance(account.getBalance().subtract(po.getAmount()));\n        accountRepository.save(account);\n        adjustBalanceRepository.delete(po);\n        return AccountVOForExtend.fromEntity(po.getAccount());\n    }\n\n    public boolean update(Integer id, BalanceFlowUpdateRequest request, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        AdjustBalance po = adjustBalanceRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        request.updatePrimitive(po);\n        adjustBalanceRepository.save(po);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/adjust_balance/AdjustBalanceVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.adjust_balance;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowVOForExtend;\n\n\npublic class AdjustBalanceVOForList extends BalanceFlowVOForExtend {\n\n    public static AdjustBalanceVOForList fromEntity(AdjustBalance po) {\n        AdjustBalanceVOForList vo = new AdjustBalanceVOForList();\n        vo.setValue(po);\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/aop/TestAspect.java",
    "content": "package com.jiukuaitech.bookkeeping.user.aop;\n\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.context.annotation.Configuration;\n\nimport java.util.Arrays;\n\n@Aspect\n@Configuration\npublic class TestAspect {\n\n    private Logger logger = LoggerFactory.getLogger(this.getClass());\n\n    @Before(\"execution(* com.jiukuaitech.bookkeeping.user.service.*.*(..))\")\n    public void before(JoinPoint joinPoint) {\n        logger.info(\" Check before-------------------------------------------------------- \");\n        logger.info(\" Allowed execution for {}\", \"\");\n        logger.info(Arrays.toString(joinPoint.getArgs()));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/asset_account/AssetAccount.java",
    "content": "package com.jiukuaitech.bookkeeping.user.asset_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\n\n@Entity\n@DiscriminatorValue(value = \"4\")\n@Getter\n@Setter\npublic class AssetAccount extends Account {\n\n    @TimeValidator\n    private Long asOfDate; //截止时间，资产余额对应的时间\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/asset_account/AssetAccountController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.asset_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.account.AccountService;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport com.jiukuaitech.bookkeeping.user.account.AccountAddRequest;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n\n@RestController\n@RequestMapping(\"/asset-accounts\")\npublic class AssetAccountController extends BaseController {\n\n    @Resource\n    private AssetAccountService assetAccountService;\n\n    @Resource\n    private AccountService accountService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid AccountQueryRequest request,\n            @PageableDefault(sort = \"balance\", direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(assetAccountService.query(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody AccountAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.add(4, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/sum\")\n    public BaseResponse handleSum(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(assetAccountService.sum(userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/asset_account/AssetAccountRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.asset_account;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface AssetAccountRepository extends BaseRepository<AssetAccount, Integer> {\n\n/*\n    @Query(\"SELECT COALESCE(SUM(p.balance), 0), \" +\n            \"COALESCE(SUM(p.expense), 0), \" +\n            \"COALESCE(SUM(p.income), 0), \" +\n            \"COALESCE(SUM(p.transferFrom), 0), \" +\n            \"COALESCE(SUM(p.transferTo), 0) \" +\n            \"FROM AssetAccount p WHERE p.book = :book AND p.enable = true\")\n    List<BigDecimal[]> findSum(Book book);\n\n    @Query(\"SELECT COALESCE(SUM(p.balance), 0) FROM AssetAccount p WHERE p.book = :book AND p.enable = true AND p.include = true\")\n    BigDecimal findSumBalance(Book book);\n*/\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/asset_account/AssetAccountService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.asset_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.account.AccountQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.account.AccountSpec;\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.account.AccountSumVO;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.List;\n\n@Service\npublic class AssetAccountService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private AssetAccountRepository assetAccountRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    public Page<AssetAccountVOForList> query(AccountQueryRequest request, Pageable page, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Specification<AssetAccount> specification = AccountSpec.buildSpecification(request, group);\n        Page<AssetAccount> poPage = assetAccountRepository.findAll(specification, page);\n        Page<AssetAccountVOForList> voPage = poPage.map(AssetAccountVOForList::fromEntity);\n        voPage.map(vo->{\n            vo.setConvertedBalance(currencyService.convert(vo.getBalance(), vo.getCurrencyCode(), group.getDefaultCurrencyCode()));\n            return vo;\n        });\n        return voPage;\n    }\n\n    public AccountSumVO sum(Integer userSignInId) {\n        AccountSumVO vo = new AccountSumVO();\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<AssetAccount> accounts = assetAccountRepository.findAll(AccountSpec.inGroupAndEnable(group));\n        BigDecimal balance = BigDecimal.ZERO;\n        for (int i = 0; i < accounts.size(); i++) {\n            Account account = accounts.get(i);\n            balance = balance.add(currencyService.convert(account.getBalance(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n        }\n        vo.setBalance(balance);\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/asset_account/AssetAccountVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.asset_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountVOForExtend;\nimport lombok.Getter;\nimport lombok.Setter;\n\n\n@Getter\n@Setter\npublic class AssetAccountVOForList extends AccountVOForExtend {\n\n    private Long asOfDate;\n\n    public static AssetAccountVOForList fromEntity(AssetAccount po) {\n        AssetAccountVOForList vo = new AssetAccountVOForList();\n        vo.setValue(po);\n        vo.setAsOfDate(po.getAsOfDate());\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/AccountInvalidateException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\npublic class AccountInvalidateException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/AmountInvalidateException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\npublic class AmountInvalidateException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlow.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.base.HasBookEntity;\nimport com.jiukuaitech.bookkeeping.user.flow_images.FlowImage;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.validation.*;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@Entity\n@Table(name=\"t_balance_flow\")\n@Inheritance(strategy = InheritanceType.SINGLE_TABLE)\n@DiscriminatorColumn(name = \"type\", discriminatorType = DiscriminatorType.INTEGER, columnDefinition = \"TINYINT(1)\")\n@Getter\n@Setter\npublic class BalanceFlow extends HasBookEntity {\n\n    @Column(nullable = false)\n    @NotNull\n    @AmountValidator\n    private BigDecimal amount; // 金额\n\n    @AmountValidator\n    private BigDecimal convertedAmount; //汇率换算之后的金额\n\n    @ManyToOne(fetch = FetchType.LAZY, optional = false)\n    @NotNull\n    private Account account;\n\n    @Column(nullable = false)\n    @NotNull\n    @TimeValidator\n    private Long createTime;\n\n    @Column(nullable = false, columnDefinition = \"TINYINT(1)\")\n    @NotNull\n    @TransactionStatusValidator\n    private Integer status = 1; // 1正常 2是待确认 3是已退款\n\n    @Column(insertable = false, updatable = false)\n    private Integer type; //1支出，2收入，3转账，4余额调整\n\n    @Column(length = 16)\n    @DescriptionValidator\n    private String description; //描述\n\n    @Column(length = 1024)\n    @NotesValidator\n    private String notes; //备注\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @NotNull\n    private User creator;\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @NotNull\n    private Group group;\n\n    @OneToMany(mappedBy = \"flow\", fetch = FetchType.LAZY)\n    private Set<FlowImage> images = new HashSet<>();\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.validation.DescriptionValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.time.Instant;\n\n@Getter\n@Setter\npublic class BalanceFlowAddRequest {\n\n    @TimeValidator\n    private Long createTime;\n\n    @DescriptionValidator\n    private String description;\n\n    @NotesValidator\n    private String notes;\n\n    public void copyPrimitive(BalanceFlow po) {\n        if (createTime == null) {\n            po.setCreateTime(Instant.now().toEpochMilli());\n        } else {\n            po.setCreateTime(createTime);\n        }\n        po.setDescription(description);\n        po.setNotes(notes);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\nimport java.util.Set;\n\n@RestController\n@RequestMapping(\"/flows\")\npublic class BalanceFlowController {\n\n    @Resource\n    private BalanceFlowService balanceFlowService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid BalanceFlowQueryRequest request,\n            @PageableDefault(sort = {\"createTime\", \"id\"}, direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceFlowService.queryWithDefaultBook(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"audit\")\n    public BaseResponse handleAudit(\n            @Valid BalanceFlowQueryRequest request,\n            @PageableDefault(sort = {\"createTime\", \"id\"}, direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceFlowService.query(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/{id}\")\n    public BaseResponse handleGet(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceFlowService.get(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(\n            @PathVariable(\"id\") Integer id,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceFlowService.remove(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/confirm\")\n    public BaseResponse handleConfirm(\n            @PathVariable(\"id\") Integer id,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceFlowService.confirm(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/{id}/images\")\n    public BaseResponse handleImages(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceFlowService.getImages(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"/{id}/images\")\n    public BaseResponse handleUpdateImages(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody Set<Integer> images,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceFlowService.updateImages(id, images, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"/{id}/image\")\n    public BaseResponse handleAddImage(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody Integer imageId,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceFlowService.addImage(id, imageId, userSignInId));\n    }\n\n}"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class BalanceFlowExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = AccountInvalidateException.class)\n    @ResponseBody\n    public BaseResponse handleException(AccountInvalidateException e) {\n        return new ErrorResponse(702, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = StatusNotValidateException.class)\n    @ResponseBody\n    public BaseResponse handleException(StatusNotValidateException e) {\n        return new ErrorResponse(703, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = AmountInvalidateException.class)\n    @ResponseBody\n    public BaseResponse handleException(AmountInvalidateException e) {\n        return new ErrorResponse(704, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowQueryRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport java.util.Set;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class BalanceFlowQueryRequest {\n\n    private Double minAmount;\n    private Double maxAmount;\n    private Long minTime;\n    private Long maxTime;\n\n    private Integer bookId;\n    private Integer accountId;\n    private Set<Integer> accounts;\n    private Integer status;\n    private Integer type;\n    private String description;\n    private Integer creatorId;\n    private Set<Integer> tags;\n    private Set<Integer> categories;\n    private Set<Integer> payees;\n\n    private Set<Integer> fromAccounts;\n    private Set<Integer> toAccounts;\n\n    // 统计支出分类用到\n    private Integer categoryId;\n\n    // 按id查询，比如找出退款对应的两条记录\n    private Set<Integer> id;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowQueryResultVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.data.domain.Page;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class BalanceFlowQueryResultVO {\n\n    private Page<BalanceFlowVOForList> result;\n    private BigDecimal expense;\n    private BigDecimal income;\n\n    public BigDecimal getSurplus() {\n        return income.subtract(expense);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface BalanceFlowRepository extends HasBookRepository<BalanceFlow> {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.expense.Expense;\nimport com.jiukuaitech.bookkeeping.user.flow_images.FlowImageRepository;\nimport com.jiukuaitech.bookkeeping.user.flow_images.FlowImageVOForList;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.income.Income;\nimport com.jiukuaitech.bookkeeping.user.transfer.Transfer;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.adjust_balance.AdjustBalanceService;\nimport com.jiukuaitech.bookkeeping.user.deal.DealService;\nimport com.jiukuaitech.bookkeeping.user.deal.DealSpec;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.expense.Expense_;\nimport com.jiukuaitech.bookkeeping.user.flow_images.FlowImage;\nimport com.jiukuaitech.bookkeeping.user.income.Income_;\nimport com.jiukuaitech.bookkeeping.user.transfer.TransferService;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n@Service\npublic class BalanceFlowService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private BalanceFlowRepository balanceFlowRepository;\n\n    @Resource\n    private DealService dealService;\n\n    @Resource\n    private TransferService transferService;\n\n    @Resource\n    private AdjustBalanceService adjustBalanceService;\n\n    @Resource\n    private FlowImageRepository flowImageRepository;\n\n    public BalanceFlowQueryResultVO queryWithDefaultBook(BalanceFlowQueryRequest request, Pageable page, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        request.setBookId(user.getDefaultBook().getId());\n        return query(request, page, userSignInId);\n    }\n\n    public BalanceFlowQueryResultVO query(BalanceFlowQueryRequest request, Pageable page, Integer userSignInId) {\n        BalanceFlowQueryResultVO result = new BalanceFlowQueryResultVO();\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Specification<BalanceFlow> specification = BalanceFlowSpec.buildFlowSpecification(request, group);\n        Page<BalanceFlow> poPage = balanceFlowRepository.findAll(specification, page);\n        Page<BalanceFlowVOForList> voPage = poPage.map(flow-> {\n            BalanceFlowVOForList vo = BalanceFlowVOForList.fromEntity(flow);\n            vo.setCurrencyCode(flow.getAccount().getCurrencyCode());\n            if (flow.getType() == 1 || flow.getType() == 2) {\n                if (flow.getBook().getDefaultCurrencyCode().equals(flow.getAccount().getCurrencyCode())) {\n                    vo.setNeedConvert(false);\n                } else {\n                    vo.setNeedConvert(true);\n                    vo.setToCurrencyCode(flow.getBook().getDefaultCurrencyCode());\n                }\n            } else if (flow.getType() == 3) {\n                String toCurrencyCode = ((Transfer) flow).getTo().getCurrencyCode();\n                if (flow.getAccount().getCurrencyCode().equals(toCurrencyCode)) {\n                    vo.setNeedConvert(false);\n                } else {\n                    vo.setNeedConvert(true);\n                    vo.setToCurrencyCode(toCurrencyCode);\n                }\n            } else {\n                vo.setNeedConvert(false);\n            }\n            return vo;\n        });\n        result.setResult(voPage);\n\n        Specification<Expense> specification1 = DealSpec.buildSpecification(request, group);\n        if (request.getType() == null || request.getType() == 1) { //只有支出类型才需要计算总额\n            result.setExpense(balanceFlowRepository.calcAggregate(specification1, Expense_.convertedAmount, Expense.class));\n        } else { //不是查询的支出类型，则支出总额肯定为空\n            result.setExpense(BigDecimal.ZERO);\n        }\n        Specification<Income> specification2 = DealSpec.buildSpecification(request, group);\n        if (request.getType() == null || request.getType() == 2) {\n            result.setIncome(balanceFlowRepository.calcAggregate(specification2, Income_.convertedAmount, Income.class));\n        } else {\n            result.setIncome(BigDecimal.ZERO);\n        }\n        return result;\n    }\n\n    public BalanceFlowVOForList get(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        BalanceFlow flow = balanceFlowRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        BalanceFlowVOForList vo = BalanceFlowVOForList.fromEntity(flow);\n        vo.setCurrencyCode(flow.getAccount().getCurrencyCode());\n        if (flow.getType() == 1 || flow.getType() == 2) {\n            if (flow.getBook().getDefaultCurrencyCode().equals(flow.getAccount().getCurrencyCode())) {\n                vo.setNeedConvert(false);\n            } else {\n                vo.setNeedConvert(true);\n                vo.setToCurrencyCode(flow.getBook().getDefaultCurrencyCode());\n            }\n        } else if (flow.getType() == 3) {\n            String toCurrencyCode = ((Transfer) flow).getTo().getCurrencyCode();\n            if (flow.getAccount().getCurrencyCode().equals(toCurrencyCode)) {\n                vo.setNeedConvert(false);\n            } else {\n                vo.setNeedConvert(true);\n                vo.setToCurrencyCode(toCurrencyCode);\n            }\n        } else {\n            vo.setNeedConvert(false);\n        }\n        return vo;\n    }\n\n    public boolean remove(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        BalanceFlow flow = balanceFlowRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        switch (flow.getType()) {\n            case 1:\n                return dealService.remove(1, id, userSignInId);\n            case 2:\n                return dealService.remove(2, id, userSignInId);\n            case 3:\n                return transferService.remove(id, userSignInId);\n            case 4:\n                adjustBalanceService.remove(id, userSignInId);\n                return true;\n            default:\n                throw new ItemNotFoundException();\n        }\n    }\n\n    public boolean confirm(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        BalanceFlow flow = balanceFlowRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        switch (flow.getType()) {\n            case 1:\n                return dealService.confirm(1, id, userSignInId);\n            case 2:\n                return dealService.confirm(2, id, userSignInId);\n            case 3:\n                return transferService.confirm(id, userSignInId);\n            default:\n                throw new ItemNotFoundException();\n        }\n    }\n\n    public boolean updateImages(Integer id, Set<Integer> images, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        BalanceFlow po = balanceFlowRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        po.getImages().forEach(i -> {\n            i.setFlow(null);\n            flowImageRepository.save(i);\n        });\n        images.forEach(i -> {\n            FlowImage image = flowImageRepository.getById(i);\n            image.setFlow(po); //必须加上这个才能关联上\n            po.getImages().add(image);\n        });\n        balanceFlowRepository.save(po);\n        return true;\n    }\n\n    public List<FlowImageVOForList> getImages(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        BalanceFlow po = balanceFlowRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        List<FlowImage> images = flowImageRepository.findByFlow(po);\n        return images.stream().map(FlowImageVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    public boolean addImage(Integer id, Integer imageId, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        BalanceFlow po = balanceFlowRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        FlowImage image = flowImageRepository.getById(imageId);\n        image.setFlow(po);\n        flowImageRepository.save(image);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelation;\nimport com.jiukuaitech.bookkeeping.user.deal.Deal;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.payee.Payee;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelation;\nimport com.jiukuaitech.bookkeeping.user.transfer.Transfer;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableSpec;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelation_;\nimport com.jiukuaitech.bookkeeping.user.deal.Deal_;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelation_;\nimport com.jiukuaitech.bookkeeping.user.transaction.Transaction;\nimport com.jiukuaitech.bookkeeping.user.transaction.Transaction_;\nimport com.jiukuaitech.bookkeeping.user.transfer.Transfer_;\nimport com.jiukuaitech.bookkeeping.user.utils.CalendarUtils;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.util.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\nimport javax.persistence.criteria.*;\nimport java.util.Set;\n\npublic final class BalanceFlowSpec {\n    \n    public static<T extends BalanceFlow> Specification<T> amountGreaterThanOrEqualTo(Double amount) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.ge(root.get(BalanceFlow_.AMOUNT), amount);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> amountLessThanOrEqualTo(Double amount) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.le(root.get(BalanceFlow_.AMOUNT), amount);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> createTimeGreaterThanOrEqualTo(Long time) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.ge(root.get(BalanceFlow_.CREATE_TIME), time);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> createTimeLessThanOrEqualTo(Long time) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.le(root.get(BalanceFlow_.CREATE_TIME), time);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> creatorEqual(User user) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(BalanceFlow_.CREATOR), user);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> accountIn(Set<Integer> accounts) {\n        return (root, query, criteriaBuilder) -> {\n            CriteriaBuilder.In<Account> in = criteriaBuilder.in(root.get(BalanceFlow_.ACCOUNT));\n            accounts.forEach(i -> in.value(new Account(i)));\n            return in;\n        };\n    }\n\n    public static<T extends BalanceFlow> Specification<T> idIn(Set<Integer> id) {\n        return (root, query, criteriaBuilder) -> {\n            CriteriaBuilder.In<Integer> in = criteriaBuilder.in(root.get(BalanceFlow_.ID));\n            id.forEach(i -> in.value(i));\n            return in;\n        };\n    }\n\n    public static<T extends BalanceFlow> Specification<T> typeEqual(Integer type) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(BalanceFlow_.TYPE), type);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> isGroup(Group group) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(BalanceFlow_.GROUP), group);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> statusEqual(Integer status) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(BalanceFlow_.STATUS), status);\n    }\n\n    // 不是待确认的状态\n    public static<T extends BalanceFlow> Specification<T> statusConfirmed() {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.notEqual(root.get(BalanceFlow_.STATUS), 2);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> descriptionEqual(String description) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(BalanceFlow_.DESCRIPTION), description);\n    }\n\n    public static<T extends BalanceFlow> Specification<T> distinct() {\n        return (root, query, cb) -> {\n            query.distinct(true);\n//            query.groupBy(root.get(BalanceFlow_.id));\n            return null;\n        };\n    }\n\n    private static<T extends BalanceFlow> Specification<T> buildBaseSpecification(BalanceFlowQueryRequest request, Group group) {\n        Specification<T> specification = isGroup(group);\n        if (request.getBookId() != null) {\n            specification = specification.and(BookNameNotesEnableSpec.isBook(new Book(request.getBookId())));\n        }\n        if (request.getMinAmount() != null) {\n            specification = specification.and(amountGreaterThanOrEqualTo(request.getMinAmount()));\n        }\n        if (request.getMaxAmount() != null) {\n            specification = specification.and(amountLessThanOrEqualTo(request.getMaxAmount()));\n        }\n        if (request.getMinTime() != null) {\n            specification = specification.and(createTimeGreaterThanOrEqualTo(CalendarUtils.getStartOfDay(request.getMinTime())));\n        }\n        if (request.getMaxTime() != null) {\n            specification = specification.and(createTimeLessThanOrEqualTo(CalendarUtils.getEndOfDay(request.getMaxTime())));\n        }\n        if (request.getCreatorId() != null) {\n            specification = specification.and(creatorEqual(new User(request.getCreatorId())));\n        }\n        if (request.getStatus() != null) {\n            specification = specification.and(statusEqual(request.getStatus()));\n        }\n        if (StringUtils.hasText(request.getDescription())) {\n            specification = specification.and(descriptionEqual(request.getDescription()));\n        }\n        if (!CollectionUtils.isEmpty(request.getId())) {\n            specification = specification.and(idIn(request.getId()));\n        }\n        specification = specification.and(distinct());\n        return specification;\n    }\n\n    // 给子类调用\n    public static<T extends BalanceFlow> Specification<T> buildSpecification(BalanceFlowQueryRequest request, Group group) {\n        Specification<T> specification = buildBaseSpecification(request, group);\n        if (!CollectionUtils.isEmpty(request.getAccounts())) {\n            specification = specification.and(accountIn(request.getAccounts()));\n        }\n        return specification;\n    }\n\n    public static Specification<BalanceFlow> buildFlowSpecification(BalanceFlowQueryRequest request, Group group) {\n        Specification<BalanceFlow> specification = buildBaseSpecification(request, group);\n\n        if (request.getAccountId() != null) {\n            specification = specification.and((Specification<BalanceFlow>) (root, query, criteriaBuilder) -> {\n                Account account = new Account(request.getAccountId());\n                Predicate predicate1 = criteriaBuilder.equal(root.get(BalanceFlow_.account), account);\n                // https://stackoverflow.com/questions/34251930/jpa-criteria-api-query-subclass-property\n//                Root<Transfer> transferRoot = criteriaBuilder.treat(root, Transfer.class);\n                @SuppressWarnings(\"unchecked\")\n                Root<Transfer> transferRoot = (Root<Transfer>) (Root<?>) root;\n                Predicate predicate2 = criteriaBuilder.equal(transferRoot.get(Transfer_.to), account);\n                return criteriaBuilder.or(predicate1, predicate2);\n            });\n        } else if (!CollectionUtils.isEmpty(request.getAccounts())) {\n            specification = specification.and((Specification<BalanceFlow>) (root, query, criteriaBuilder) -> {\n                CriteriaBuilder.In<Account> in1 = criteriaBuilder.in(root.get(BalanceFlow_.account));\n                request.getAccounts().forEach(i -> in1.value(new Account(i)));\n                @SuppressWarnings(\"unchecked\")\n                Root<Transfer> transferRoot = (Root<Transfer>) (Root<?>) root;\n                CriteriaBuilder.In<Account> in2 = criteriaBuilder.in(transferRoot.get(Transfer_.to));\n                request.getAccounts().forEach(i -> in2.value(new Account(i)));\n                return criteriaBuilder.or(in1, in2);\n            });\n        }\n\n        if (!CollectionUtils.isEmpty(request.getPayees())) {\n            specification = specification.and((Specification<BalanceFlow>) (root, query, criteriaBuilder) -> {\n//                Root<Deal> dealRoot = criteriaBuilder.treat(root, Deal.class);\n                Root<Deal> dealRoot = (Root<Deal>) (Root<?>) root;\n                CriteriaBuilder.In<Payee> in = criteriaBuilder.in(dealRoot.get(Deal_.payee));\n                request.getPayees().forEach(i -> in.value(new Payee(i)));\n                return in;\n            });\n        }\n\n        if (!CollectionUtils.isEmpty(request.getCategories())) {\n            specification = specification.and((Specification<BalanceFlow>) (root, query, criteriaBuilder) -> {\n                Root<Deal> dealRoot = (Root<Deal>) (Root<?>) root;\n                Join<Deal, CategoryRelation> join = dealRoot.join(Deal_.categories);\n                return join.get(CategoryRelation_.category).in(request.getCategories());\n            });\n        }\n\n        if (!CollectionUtils.isEmpty(request.getTags())) {\n            specification = specification.and((Specification<BalanceFlow>) (root, query, criteriaBuilder) -> {\n                Root<Transaction> transactionRoot = criteriaBuilder.treat(root, Transaction.class);\n                Join<Transaction, TagRelation> join = transactionRoot.join(Transaction_.tags);\n                return join.get(TagRelation_.tag).in(request.getTags());\n            });\n        }\n        if (request.getType() != null) {\n            specification = specification.and(typeEqual(request.getType()));\n        }\n\n        return specification;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.validation.DescriptionValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class BalanceFlowUpdateRequest {\n\n    @TimeValidator\n    private Long createTime;\n\n    @DescriptionValidator\n    private String description;\n\n    @NotesValidator\n    private String notes;\n\n    public void updatePrimitive(BalanceFlow po) {\n        if (createTime != null) po.setCreateTime(createTime);\n        if (description != null) po.setDescription(description);\n        if (notes != null) po.setNotes(notes);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowVOForExtend.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountVOForExtend;\nimport com.jiukuaitech.bookkeeping.user.utils.EnumUtils;\nimport com.jiukuaitech.bookkeeping.user.response.HasNameVO;\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class BalanceFlowVOForExtend {\n\n    private Integer id;\n    private HasNameVO book;\n    private BigDecimal amount;\n    private BigDecimal convertedAmount;\n    private String currencyCode;\n    private Boolean needConvert;\n    private String toCurrencyCode;\n    private AccountVOForExtend account;\n    private String accountName;\n    private Long createTime;\n    private String description;\n    private String notes;\n    private Integer status;\n\n    public void setValue(BalanceFlow po) {\n        setId(po.getId());\n        setBook(new HasNameVO(po.getBook().getId(), po.getBook().getName()));\n        setAmount(po.getAmount());\n        setConvertedAmount(po.getConvertedAmount());\n        if (po.getAccount() != null) setAccount(AccountVOForExtend.fromEntity(po.getAccount()));\n        setAccountName(getAccount() == null ? null : getAccount().getName());\n        setCreateTime(po.getCreateTime());\n        setDescription(po.getDescription());\n        setNotes(po.getNotes());\n        setStatus(po.getStatus());\n    }\n\n    public static BalanceFlowVOForExtend fromEntity(BalanceFlow balanceFlow) {\n        BalanceFlowVOForExtend result = new BalanceFlowVOForExtend();\n        result.setValue(balanceFlow);\n        return result;\n    }\n\n    public String getStatusName() {\n        return EnumUtils.translateFlowStatus(getStatus());\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/BalanceFlowVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\nimport com.jiukuaitech.bookkeeping.user.adjust_balance.AdjustBalance;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelationVOForList;\nimport com.jiukuaitech.bookkeeping.user.deal.DealVOForList;\nimport com.jiukuaitech.bookkeeping.user.expense.Expense;\nimport com.jiukuaitech.bookkeeping.user.income.Income;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelationVOForList;\nimport com.jiukuaitech.bookkeeping.user.transfer.Transfer;\nimport com.jiukuaitech.bookkeeping.user.utils.EnumUtils;\nimport com.jiukuaitech.bookkeeping.user.adjust_balance.AdjustBalanceVOForList;\nimport com.jiukuaitech.bookkeeping.user.response.HasNameVO;\nimport com.jiukuaitech.bookkeeping.user.transfer.TransferVOForList;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.util.StringUtils;\n\nimport java.math.BigDecimal;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n@Getter\n@Setter\npublic class BalanceFlowVOForList {\n\n    private Integer id;\n    private Integer type; // 1是expense，2是income，3是transfer，4是调整余额\n    private DealVOForList expense;\n    private DealVOForList income;\n    private TransferVOForList transfer;\n    private AdjustBalanceVOForList adjustBalance;\n\n    private BigDecimal amount;\n    private BigDecimal convertedAmount;\n    private String currencyCode;\n    private Boolean needConvert;\n    private String toCurrencyCode;\n    private Long createTime;\n    private Integer status;\n    private String description;\n\n    private Integer accountId;\n    private Integer toId;\n    private String accountName;\n    private String fromAccountName;\n    private String toAccountName;\n    private String categoryName;\n    private Set<CategoryRelationVOForList> categories = new HashSet<>();\n    private String bookName;\n    private Set<TagRelationVOForList> tags = new HashSet<>();\n    private HasNameVO payee;\n    private String notes;\n\n    public static BalanceFlowVOForList fromEntity(BalanceFlow po) {\n        if (po == null) return null;\n        BalanceFlowVOForList vo = new BalanceFlowVOForList();\n        vo.setId(po.getId());\n        vo.setBookName(po.getBook().getName());\n        vo.setType(po.getType());\n        vo.setStatus(po.getStatus());\n        vo.setDescription(po.getDescription());\n        vo.setAmount(po.getAmount());\n        vo.setConvertedAmount(po.getConvertedAmount());\n        vo.setCreateTime(po.getCreateTime());\n        vo.setNotes(po.getNotes());\n        if (po instanceof Expense) {\n            vo.setExpense(DealVOForList.fromEntity((Expense) po));\n            vo.setAccountId(vo.getExpense().getAccount().getId());\n            vo.setAccountName(vo.getExpense().getAccountName());\n            vo.setCategoryName(vo.getExpense().getCategoryName());\n            vo.setCategories(vo.getExpense().getCategories());\n            vo.setTags(vo.getExpense().getTags());\n            vo.setPayee(vo.getExpense().getPayee());\n        } else if (po instanceof Income) {\n            vo.setIncome(DealVOForList.fromEntity((Income) po));\n            vo.setAccountId(vo.getIncome().getAccount().getId());\n            vo.setAccountName(vo.getIncome().getAccountName());\n            vo.setCategoryName(vo.getIncome().getCategoryName());\n            vo.setCategories(vo.getIncome().getCategories());\n            vo.setTags(vo.getIncome().getTags());\n            vo.setPayee(vo.getIncome().getPayee());\n        } else if (po instanceof Transfer) {\n            vo.setTransfer(TransferVOForList.fromEntity((Transfer) po));\n            vo.setAccountId(vo.getTransfer().getFromId());\n            vo.setToId(vo.getTransfer().getToId());\n            vo.setAccountName(vo.getTransfer().getAccountName());\n            vo.setFromAccountName(vo.getTransfer().getFrom().getName());\n            vo.setToAccountName(vo.getTransfer().getTo().getName());\n            vo.setTags(vo.getTransfer().getTags());\n        } else if (po instanceof AdjustBalance) {\n            vo.setAdjustBalance(AdjustBalanceVOForList.fromEntity((AdjustBalance) po));\n            vo.setAccountName(vo.getAdjustBalance().getAccountName());\n        }\n        return vo;\n    }\n\n    public String getCreateTimeFormatted() {\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm\");\n        return simpleDateFormat.format(new Date(createTime));\n    }\n\n    private String getCreateDateFormatted() {\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd\");\n        return simpleDateFormat.format(new Date(createTime));\n    }\n\n    public String getAmountFormatted() {\n        return String.format(\"%.2f\", amount);\n    }\n\n    public String getTitle() {\n        StringBuilder result = new StringBuilder();\n        if (StringUtils.hasText(description)) {\n            result.append(description);\n        } else {\n            if (type == 1) {\n                result.append(expense.getCategories().stream().map(CategoryRelationVOForList::getCategoryName).collect(Collectors.joining(\", \")));\n            } else if (type == 2) {\n                result.append(income.getCategories().stream().map(CategoryRelationVOForList::getCategoryName).collect(Collectors.joining(\", \")));\n            } else if (type == 3) {\n                result.append(accountName);\n            } else {\n                result.append(\"调整余额\");\n            }\n        }\n        if (getPayee() != null) {\n            result.append(\" - \").append(getPayee().getName());\n        }\n        return result.toString();\n    }\n\n    public String getSubTitle() {\n        return getTypeName() + \" \" + getCreateDateFormatted() + \" \" + getTagsName();\n    }\n\n    public String getTagsName() {\n        if (type == 1) {\n            return expense.getTags().stream().map(TagRelationVOForList::getTagName).collect(Collectors.joining(\", \"));\n        }\n        if (type == 2) {\n            return income.getTags().stream().map(TagRelationVOForList::getTagName).collect(Collectors.joining(\", \"));\n        }\n        if (type == 3) {\n            return transfer.getTags().stream().map(TagRelationVOForList::getTagName).collect(Collectors.joining(\", \"));\n        }\n        return \"\";\n    }\n\n    public String getTypeName() {\n        return EnumUtils.translateFlowType(type);\n    }\n\n    public String getStatusName() {\n        return EnumUtils.translateFlowStatus(status);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_flow/StatusNotValidateException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_flow;\n\npublic class StatusNotValidateException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_log/BalanceLog.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_log;\n\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.validation.BalanceValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Entity\n@Table(name=\"t_balance_log\")\n@Getter\n@Setter\npublic class BalanceLog extends BaseEntity {\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"group_id\")\n    @NotNull\n    private Group group; // 账簿必须属于某个组\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @NotNull\n    private User creator;\n\n    @Column(nullable = false) //最多9亿\n    @NotNull\n    @BalanceValidator\n    private BigDecimal asset;\n\n    @Column(nullable = false) //最多9亿\n    @NotNull\n    @BalanceValidator\n    private BigDecimal debt;\n\n    @Column(nullable = false)\n    @NotNull\n    @TimeValidator\n    private Long createTime;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_log/BalanceLogAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_log;\n\nimport com.jiukuaitech.bookkeeping.user.validation.BalanceValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.Column;\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class BalanceLogAddRequest {\n\n    @NotNull\n    @BalanceValidator\n    private BigDecimal asset;\n\n    @NotNull\n    @BalanceValidator\n    private BigDecimal debt;\n\n    @Column(nullable = false)\n    @NotNull\n    @TimeValidator\n    private Long createTime;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_log/BalanceLogController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_log;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/logs\")\npublic class BalanceLogController {\n\n    @Resource\n    private BalanceLogService balanceLogService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @PageableDefault(sort = \"createTime\", direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceLogService.query(page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody BalanceLogAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceLogService.add(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(balanceLogService.remove(id, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_log/BalanceLogRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_log;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport org.springframework.stereotype.Repository;\nimport java.util.List;\nimport java.util.Optional;\n\n\n@Repository\npublic interface BalanceLogRepository extends BaseRepository<BalanceLog, Integer> {\n\n    Optional<BalanceLog> findOneByGroupAndId(Group group, Integer integer);\n\n    List<BalanceLog> findByGroupAndCreateTimeBetweenOrderByCreateTime(Group group, Long min, Long max);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_log/BalanceLogService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_log;\n\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport javax.persistence.criteria.Predicate;\nimport java.util.ArrayList;\nimport java.util.List;\n\n\n@Service\npublic class BalanceLogService {\n\n    @Resource\n    private BalanceLogRepository balanceLogRepository;\n\n    @Resource\n    private UserService userService;\n\n    public Page<BalanceLogVOForList> query(Pageable page, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Specification<BalanceLog> specification = (root, query, criteriaBuilder) -> {\n            List<Predicate> predicates = new ArrayList<>();\n            predicates.add(criteriaBuilder.equal(root.get(BalanceLog_.group), group));\n            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));\n        };\n        Page<BalanceLog> poPage = balanceLogRepository.findAll(specification, page);\n        return poPage.map(BalanceLogVOForList::fromEntity);\n    }\n\n    public BalanceLogVOForList add(BalanceLogAddRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        BalanceLog po = new BalanceLog();\n        po.setGroup(group);\n        po.setCreator(user);\n        po.setAsset(request.getAsset());\n        po.setDebt(request.getDebt());\n        po.setCreateTime(request.getCreateTime());\n        return BalanceLogVOForList.fromEntity(balanceLogRepository.save(po));\n    }\n\n    public BalanceLogVOForList remove(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        BalanceLog po = balanceLogRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        balanceLogRepository.delete(po);\n        return BalanceLogVOForList.fromEntity(po);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/balance_log/BalanceLogVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.balance_log;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class BalanceLogVOForList {\n\n    private Integer id;\n    private BigDecimal asset;\n    private BigDecimal debt;\n    private Long createTime;\n\n    public static BalanceLogVOForList fromEntity(BalanceLog po) {\n        BalanceLogVOForList vo = new BalanceLogVOForList();\n        vo.setId(po.getId());\n        vo.setAsset(po.getAsset());\n        vo.setDebt(po.getDebt());\n        vo.setCreateTime(po.getCreateTime());\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/BaseController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.annotation.Resource;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@Getter\n@Setter\npublic class BaseController {\n\n    @Resource\n    private HttpServletRequest request;\n\n    @Resource\n    private HttpServletResponse response;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/BaseEntity.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.GeneratedValue;\nimport javax.persistence.GenerationType;\nimport javax.persistence.Id;\nimport javax.persistence.MappedSuperclass;\n\n@MappedSuperclass\n@Getter\n@Setter\npublic abstract class BaseEntity {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n\n    // 不要用id重写hashcode和equals，之前的tag更新出了Bug\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/BaseRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport org.springframework.data.repository.NoRepositoryBean;\n\nimport javax.persistence.metamodel.SingularAttribute;\nimport java.io.Serializable;\nimport java.math.BigDecimal;\nimport java.util.List;\n\n/*\nhttps://www.baeldung.com/spring-data-jpa-method-in-all-repositories\nhttps://github.com/pkainulainen/spring-data-jpa-examples/blob/master/custom-method-all-repos/src/main/java/net/petrikainulainen/springdata/jpa/common/BaseRepository.java\n */\n@NoRepositoryBean\npublic interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {\n\n    // P extends Number  change to BigDecimal\n    <E> BigDecimal calcAggregate(Specification<E> spec, SingularAttribute<?, BigDecimal> column, Class<E> clazz);\n\n    <E> List<BigDecimal> calcAggregate(Specification<E> spec, List<SingularAttribute<?, BigDecimal>> columns, Class<E> clazz);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/BaseRepositoryFactoryBean.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.support.JpaRepositoryFactory;\nimport org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;\nimport org.springframework.data.repository.core.RepositoryMetadata;\nimport org.springframework.data.repository.core.support.RepositoryFactorySupport;\n\nimport javax.persistence.EntityManager;\nimport java.io.Serializable;\n\npublic class BaseRepositoryFactoryBean<R extends JpaRepository<T, I>, T,\n        I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> {\n\n    public BaseRepositoryFactoryBean(Class<? extends R> repositoryInterface) {\n        super(repositoryInterface);\n    }\n\n    @SuppressWarnings(\"rawtypes\")\n    protected RepositoryFactorySupport createRepositoryFactory(EntityManager em) {\n        return new CustomRepositoryFactory(em);\n    }\n\n    private static class CustomRepositoryFactory<T, I extends Serializable>\n            extends JpaRepositoryFactory {\n\n        private final EntityManager em;\n\n        public CustomRepositoryFactory(EntityManager em) {\n            super(em);\n            this.em = em;\n        }\n\n        @SuppressWarnings(\"unchecked\")\n        protected Object getTargetRepository(RepositoryMetadata metadata) {\n            return new BaseRepositoryImpl<T, I>(\n                    (Class<T>) metadata.getDomainType(), em);\n        }\n\n        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {\n            return BaseRepositoryImpl.class;\n        }\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/BaseRepositoryImpl.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport com.jiukuaitech.bookkeeping.user.utils.CommonUtils;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.data.jpa.repository.support.JpaEntityInformation;\nimport org.springframework.data.jpa.repository.support.SimpleJpaRepository;\n\nimport javax.persistence.EntityManager;\nimport javax.persistence.TypedQuery;\nimport javax.persistence.criteria.CriteriaBuilder;\nimport javax.persistence.criteria.CriteriaQuery;\nimport javax.persistence.criteria.Root;\nimport javax.persistence.criteria.Selection;\nimport javax.persistence.metamodel.SingularAttribute;\nimport java.io.Serializable;\nimport java.math.BigDecimal;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/*\nhttps://stackoverflow.com/questions/38934549/spring-specification-with-sum-function\n */\npublic class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {\n\n    private final EntityManager entityManager;\n\n    public BaseRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {\n        super(domainClass, entityManager);\n        this.entityManager = entityManager;\n    }\n\n    public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {\n        super(entityInformation, entityManager);\n        this.entityManager = entityManager;\n    }\n\n    @Override\n    public <E> BigDecimal calcAggregate(Specification<E> spec, SingularAttribute<?, BigDecimal> column, Class<E> clazz) {\n        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();\n        CriteriaQuery<BigDecimal> query = criteriaBuilder.createQuery(column.getJavaType());\n        Root<E> root = query.from(clazz);\n        if (spec != null) {\n            query.where(spec.toPredicate(root, query, criteriaBuilder));\n        }\n        query.select(criteriaBuilder.sum(root.get(column.getName())));\n        TypedQuery<BigDecimal> typedQuery = entityManager.createQuery(query);\n        BigDecimal result = typedQuery.getSingleResult();\n        if (result == null) return BigDecimal.valueOf(0);\n        return result;\n    }\n\n    @Override\n    public <E> List<BigDecimal> calcAggregate(Specification<E> spec, List<SingularAttribute<?, BigDecimal>> columns, Class<E> clazz) {\n        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();\n        CriteriaQuery<BigDecimal[]> query = criteriaBuilder.createQuery(BigDecimal[].class);\n        Root<E> root = query.from(clazz);\n        if (spec != null) {\n            query.where(spec.toPredicate(root, query, criteriaBuilder));\n        }\n        List<Selection<?>> selectionList = new ArrayList<>();\n        columns.forEach(i -> selectionList.add(criteriaBuilder.sum(root.get(i.getName()))));\n        query.multiselect(selectionList);\n        Object[] result = entityManager.createQuery(query).getSingleResult();\n        return CommonUtils.coalesce(Arrays.copyOf(result, result.length, BigDecimal[].class));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/BookNameNotesEnableEntity.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@MappedSuperclass\n@Getter\n@Setter\npublic abstract class BookNameNotesEnableEntity extends HasBookEntity {\n\n    @Column(length = 16, nullable = false)\n    @NotNull\n    @NameValidator\n    private String name;\n\n    @Column(length = 128)\n    @NotesValidator\n    private String notes;\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean enable = true;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/BookNameNotesEnableSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport org.springframework.data.jpa.domain.Specification;\n\nimport javax.persistence.criteria.CriteriaBuilder;\nimport java.util.Set;\n\npublic final class BookNameNotesEnableSpec {\n\n    public static<T> Specification<T> isBook(Book book) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(BookNameNotesEnableEntity_.BOOK), book);\n    }\n\n    public static<T> Specification<T> inBooks(Set<Integer> books) {\n        return (root, query, criteriaBuilder) -> {\n            CriteriaBuilder.In<Integer> in = criteriaBuilder.in(root.get(BookNameNotesEnableEntity_.BOOK));\n            books.forEach(i -> in.value(i));\n            return in;\n        };\n    }\n\n    public static<T> Specification<T> enable() {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(BookNameNotesEnableEntity_.ENABLE), true);\n    }\n\n    public static<T> Specification<T> isEnable(Boolean enable) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(BookNameNotesEnableEntity_.ENABLE), enable);\n    }\n\n    public static<T> Specification<T> nameLike(String name) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get(BookNameNotesEnableEntity_.NAME), \"%\"+name+\"%\");\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/HasBookEntity.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.FetchType;\nimport javax.persistence.JoinColumn;\nimport javax.persistence.ManyToOne;\nimport javax.persistence.MappedSuperclass;\nimport javax.validation.constraints.NotNull;\n\n@MappedSuperclass\n@Getter\n@Setter\npublic abstract class HasBookEntity extends BaseEntity {\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"book_id\")\n    @NotNull\n    private Book book;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/HasBookRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport org.springframework.data.repository.NoRepositoryBean;\nimport java.util.Optional;\n\n@NoRepositoryBean\npublic interface HasBookRepository<T extends HasBookEntity> extends BaseRepository<T, Integer>  {\n\n    Optional<T> findOneByBookAndId(Book book, Integer id);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/JpaDataConfig.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.jpa.repository.config.EnableJpaRepositories;\nimport org.springframework.data.web.config.EnableSpringDataWebSupport;\n\n@Configuration\n@EnableJpaRepositories(basePackages = \"com.jiukuaitech.bookkeeping.user\",\n        repositoryFactoryBeanClass = BaseRepositoryFactoryBean.class)\n@EnableSpringDataWebSupport\npublic class JpaDataConfig {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/NameNotesEnableEntity.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.Column;\nimport javax.persistence.MappedSuperclass;\nimport javax.validation.constraints.NotNull;\n\n@MappedSuperclass\n@Getter\n@Setter\npublic abstract class NameNotesEnableEntity extends BaseEntity {\n\n    @Column(length = 16, nullable = false)\n    @NotNull\n    @NameValidator\n    private String name;\n\n    @Column(length = 1024)\n    @NotesValidator\n    private String notes;\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean enable = true;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/base/TestController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.base;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.servlet.support.ServletUriComponentsBuilder;\n\nimport javax.servlet.http.HttpServletRequest;\n\n\n@RestController\npublic class TestController {\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/test1\")\n    public BaseResponse handleTest1() {\n        return new DataResponse<>(19);\n    }\n\n    @GetMapping(\"/test2\")\n    public BaseResponse getBaseUrl(HttpServletRequest request) {\n        String baseUrl = ServletUriComponentsBuilder\n                .fromRequestUri(request)\n                .replacePath(null)\n                .build()\n                .toUriString();\n        return new DataResponse<>(baseUrl);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/Book.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\nimport com.jiukuaitech.bookkeeping.user.expense_category.ExpenseCategory;\nimport com.jiukuaitech.bookkeeping.user.income_category.IncomeCategory;\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.base.NameNotesEnableEntity;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name=\"t_book\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"group_id\", \"name\"})})\n@Getter\n@Setter\n/**\n * 账簿类，它属于某个组管理。下面会有很多账户。\n * group+name决定一个账簿。\n */\npublic class Book extends NameNotesEnableEntity {\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"group_id\")\n    @NotNull\n    private Group group; // 账簿必须属于某个组\n\n    @OneToOne\n    private Account defaultExpenseAccount;\n\n    @OneToOne\n    private Account defaultIncomeAccount;\n\n    @OneToOne\n    private Account defaultTransferFromAccount;\n\n    @OneToOne\n    private Account defaultTransferToAccount;\n\n    @OneToOne\n    private ExpenseCategory defaultExpenseCategory;\n\n    @OneToOne\n    private IncomeCategory defaultIncomeCategory;\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean descriptionEnable = true;\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean timeEnable = false;\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean imageEnable = false;\n\n    @Column(nullable = false, length = 8)\n    @NotNull\n    private String defaultCurrencyCode;//默认的币种\n\n    public Book() { }\n\n    public Book(Integer id) {\n        super.setId(id);\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/BookAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotBlank;\n\n@Getter\n@Setter\npublic class BookAddRequest {\n\n    @NotBlank\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    private Boolean descriptionEnable = true;\n    private Boolean timeEnable = false;\n    private Boolean imageEnable = false;\n\n    @NotBlank\n    private String defaultCurrencyCode;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/BookController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/books\")\npublic class BookController extends BaseController {\n\n    @Resource\n    private BookService bookService;\n\n    /*\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleGetAll(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(bookService.getAll(userSignInId));\n    }\n    */\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @PageableDefault(sort = \"id\", direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(bookService.query(page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/{id}\")\n    public BaseResponse handleGet(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(bookService.get(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(@Valid @RequestBody BookAddRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(bookService.add(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody BookUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(bookService.update(id, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/config\")\n    public BaseResponse handleConfig(\n            @Valid @RequestBody BookUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(bookService.config(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(bookService.remove(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggle\")\n    public BaseResponse handleToggle(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(bookService.toggle(id, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/BookExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class BookExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = BookMaxCountException.class)\n    @ResponseBody\n    public BaseResponse handleException(BookMaxCountException e) {\n        return new ErrorResponse(602, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/BookMaxCountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\npublic class BookMaxCountException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/BookRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport org.springframework.stereotype.Repository;\nimport java.util.Optional;\n\n@Repository\npublic interface BookRepository extends BaseRepository<Book, Integer> {\n    \n    Integer countByGroup_id(Integer groupId);\n\n    Optional<Book> findOneByGroupAndId(Group group, Integer id);\n\n    Optional<Book> findOneByGroupAndName(Group group, String name);\n\n    long deleteByGroup_id(Integer groupId);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/BookService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.expense_category.ExpenseCategory;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.group.GroupRepository;\nimport com.jiukuaitech.bookkeeping.user.income_category.IncomeCategory;\nimport com.jiukuaitech.bookkeeping.user.user.*;\nimport com.jiukuaitech.bookkeeping.user.account.AccountRepository;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.exception.NameExistsException;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.StringUtils;\nimport javax.annotation.Resource;\nimport javax.persistence.criteria.Predicate;\n\n@Service\npublic class BookService {\n\n    @Resource\n    private UserRepository userRepository;\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private GroupRepository groupRepository;\n\n    @Resource\n    private BookRepository bookRepository;\n\n    @Resource\n    private AccountRepository accountRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    @Value(\"${book.max.count}\")\n    private Integer maxCount;\n\n    public Page<BookVOForList> query(Pageable page, Integer userSignInId) {\n        Specification<Book> specification = (root, query, criteriaBuilder) -> {\n            List<Predicate> predicates = new ArrayList<>();\n            User user = userService.getUser(userSignInId);\n            predicates.add(criteriaBuilder.equal(root.get(Book_.group), user.getDefaultGroup()));\n            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));\n        };\n        Page<Book> poPage = bookRepository.findAll(specification, page);\n        return poPage.map(BookVOForList::fromEntity);\n    }\n\n    public BookVOForList get(Integer id, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Book po = bookRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        return BookVOForList.fromEntity(po);\n    }\n\n    public boolean add(BookAddRequest request, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        if (bookRepository.findOneByGroupAndName(group, request.getName()).isPresent()) {\n            throw new NameExistsException();\n        }\n        if (bookRepository.countByGroup_id(group.getId()) >= maxCount) {\n            throw new BookMaxCountException();\n        }\n        currencyService.checkCode(request.getDefaultCurrencyCode());\n        Book po = new Book();\n        po.setName(request.getName());\n        po.setDefaultCurrencyCode(request.getDefaultCurrencyCode());\n        po.setNotes(request.getNotes());\n        po.setGroup(group);\n        po.setDescriptionEnable(request.getDescriptionEnable());\n        po.setTimeEnable(request.getTimeEnable());\n        po.setImageEnable(request.getImageEnable());\n        bookRepository.save(po);\n        return true;\n    }\n\n    public BookVOForList update(Integer id, BookUpdateRequest request, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Book po = bookRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        if (StringUtils.hasText(request.getName())) {\n            if (!po.getName().equals(request.getName())) {\n                if (bookRepository.findOneByGroupAndName(po.getGroup(), request.getName()).isPresent()) {\n                    throw new NameExistsException();\n                }\n            }\n        }\n        if (StringUtils.hasText(request.getName())) po.setName(request.getName());\n        if (request.getNotes() != null) po.setNotes(request.getNotes());\n        bookRepository.save(po);\n        return BookVOForList.fromEntity(po);\n    }\n\n    public BookVOForList config(BookUpdateRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book po = user.getDefaultBook();\n        // 不检查默认的账户和分类是不是该账本下的了，后果就是无法记账默认值而已。\n        if (request.getDefaultExpenseAccountId() != null) {\n            po.setDefaultExpenseAccount(accountRepository.getById(request.getDefaultExpenseAccountId()));\n        } else {\n            po.setDefaultExpenseAccount(null);\n        }\n\n        if (request.getDefaultIncomeAccountId() != null) {\n            po.setDefaultIncomeAccount(accountRepository.getById(request.getDefaultIncomeAccountId()));\n        } else {\n            po.setDefaultIncomeAccount(null);\n        }\n\n        if (request.getDefaultTransferFromAccountId() != null) {\n            po.setDefaultTransferFromAccount(accountRepository.getById(request.getDefaultTransferFromAccountId()));\n        } else {\n            po.setDefaultTransferFromAccount(null);\n        }\n\n        if (request.getDefaultTransferToAccountId() != null) {\n            po.setDefaultTransferToAccount(accountRepository.getById(request.getDefaultTransferToAccountId()));\n        } else {\n            po.setDefaultTransferToAccount(null);\n        }\n\n        if (request.getDefaultExpenseCategoryId() != null) {\n            po.setDefaultExpenseCategory(new ExpenseCategory(request.getDefaultExpenseCategoryId()));\n        } else {\n            po.setDefaultExpenseCategory(null);\n        }\n\n        if (request.getDefaultIncomeCategoryId() != null) {\n            po.setDefaultIncomeCategory(new IncomeCategory(request.getDefaultIncomeCategoryId()));\n        } else {\n            po.setDefaultIncomeCategory(null);\n        }\n\n        if (request.getDescriptionEnable() != null) po.setDescriptionEnable(request.getDescriptionEnable());\n        if (request.getTimeEnable() != null) po.setTimeEnable(request.getTimeEnable());\n        if (request.getImageEnable() != null) po.setImageEnable(request.getImageEnable());\n\n        bookRepository.save(po);\n        return BookVOForList.fromEntity(po);\n    }\n\n    public boolean remove(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Book po = bookRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n//        UserGroupRelation userGroupRelation = userGroupRelationRepository.findOneByUserAndGroup(user, group);\n//        if (userGroupRelation == null || userGroupRelation.getRole() != 1) {\n//            throw new PermissionException(\"No Permission\");\n//        }\n        if (user.getDefaultBook().getId().equals(po.getId())) {\n            user.setDefaultBook(null);\n            userRepository.save(user);\n        }\n        if (group.getDefaultBook().getId().equals(po.getId())) {\n            group.setDefaultBook(null);\n            groupRepository.save(group);\n        }\n        bookRepository.delete(po);\n        return true;\n    }\n\n    public boolean toggle(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Book po = bookRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        if (user.getDefaultBook().equals(po)) {\n            throw new ItemNotFoundException();\n        }\n        po.setEnable(!po.getEnable());\n        bookRepository.save(po);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/BookUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class BookUpdateRequest {\n\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    private Integer defaultExpenseAccountId;\n    private Integer defaultIncomeAccountId;\n    private Integer defaultTransferFromAccountId;\n    private Integer defaultTransferToAccountId;\n    private Integer defaultExpenseCategoryId;\n    private Integer defaultIncomeCategoryId;\n    private Boolean descriptionEnable;\n    private Boolean timeEnable;\n    private Boolean imageEnable;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/book/BookVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.book;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountVOForExtend;\nimport com.jiukuaitech.bookkeeping.user.response.HasNameVO;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class BookVOForList {\n\n    private Integer id;\n    private String name;\n    private String notes;\n    private HasNameVO group;\n    private AccountVOForExtend defaultExpenseAccount;\n    private AccountVOForExtend defaultIncomeAccount;\n    private AccountVOForExtend defaultTransferFromAccount;\n    private AccountVOForExtend defaultTransferToAccount;\n    private HasNameVO defaultExpenseCategory;\n    private HasNameVO defaultIncomeCategory;\n    private Boolean descriptionEnable;\n    private Boolean timeEnable;\n    private Boolean imageEnable;\n    private Boolean enable;\n    private String defaultCurrencyCode;\n\n    public static BookVOForList fromEntity(Book po) {\n        BookVOForList vo = new BookVOForList();\n        vo.setId(po.getId());\n        vo.setName(po.getName());\n        vo.setDefaultCurrencyCode(po.getDefaultCurrencyCode());\n        vo.setNotes(po.getNotes());\n        vo.setGroup(new HasNameVO(po.getGroup().getId(), po.getGroup().getName()));\n        vo.setDescriptionEnable(po.getDescriptionEnable());\n        vo.setTimeEnable(po.getTimeEnable());\n        vo.setImageEnable(po.getImageEnable());\n        vo.setEnable(po.getEnable());\n        if (po.getDefaultExpenseAccount() != null) {\n            vo.setDefaultExpenseAccount(AccountVOForExtend.fromEntity(po.getDefaultExpenseAccount()));\n        }\n        if (po.getDefaultIncomeAccount() != null) {\n            vo.setDefaultIncomeAccount(AccountVOForExtend.fromEntity(po.getDefaultIncomeAccount()));\n        }\n        if (po.getDefaultTransferFromAccount() != null) {\n            vo.setDefaultTransferFromAccount(AccountVOForExtend.fromEntity(po.getDefaultTransferFromAccount()));\n        }\n        if (po.getDefaultTransferToAccount() != null) {\n            vo.setDefaultTransferToAccount(AccountVOForExtend.fromEntity(po.getDefaultTransferToAccount()));\n        }\n        if (po.getDefaultExpenseCategory() != null) {\n            vo.setDefaultExpenseCategory(new HasNameVO(po.getDefaultExpenseCategory().getId(), po.getDefaultExpenseCategory().getName()));\n        }\n        if (po.getDefaultIncomeCategory() != null) {\n            vo.setDefaultIncomeCategory(new HasNameVO(po.getDefaultIncomeCategory().getId(), po.getDefaultIncomeCategory().getName()));\n        }\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/Category.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableEntity;\nimport lombok.Getter;\nimport lombok.NoArgsConstructor;\nimport lombok.Setter;\nimport org.springframework.util.CollectionUtils;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Entity\n@Table(name=\"t_category\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"parent_id\", \"name\"})})\n@Inheritance(strategy = InheritanceType.SINGLE_TABLE)\n@DiscriminatorColumn(name = \"type\", discriminatorType = DiscriminatorType.INTEGER, columnDefinition = \"TINYINT(1)\")\n@Getter\n@Setter\n@NoArgsConstructor\npublic class Category extends BookNameNotesEnableEntity {\n\n    @Column(insertable = false, updatable = false)\n    private Integer type; //1支出分类，2收入分类\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"parent_id\")\n    private Category parent;\n    \n    @Column(nullable = false)\n    @NotNull\n    private Integer level;\n\n    @OneToMany(mappedBy = \"parent\", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)\n    private List<Category> children = new ArrayList<>();\n\n    public Category(Integer id) {\n        super.setId(id);\n    }\n\n    public List<Category> getChildren(List<Category> categories) {\n        List<Category> result = new ArrayList<>();\n        for (Category item : categories) {\n            if (item.getParent() != null && this.getId().equals(item.getParent().getId())) {\n                result.add(item);\n            }\n        }\n        return result;\n    }\n\n    public List<Category> getOffspring(List<Category> categories) {\n        List<Category> result = new ArrayList<>();\n        List<Category> children = this.getChildren(categories);\n        if (!CollectionUtils.isEmpty(children)) {\n            result.addAll(children);\n            for (Category item : children) {\n                result.addAll(item.getOffspring(categories));\n            }\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotBlank;\n\n@Getter\n@Setter\npublic class CategoryAddRequest {\n\n    @NotBlank\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    private Integer parentId;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\n\n@RestController\n@RequestMapping(\"/categories\")\npublic class CategoryController extends BaseController {\n\n    @Resource\n    private CategoryService categoryService;\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(categoryService.remove(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggle\")\n    public BaseResponse handleToggle(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(categoryService.toggle(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/{id}\")\n    public BaseResponse handleGet(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(categoryService.get(id, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class CategoryExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = ParentCategoryNotEnableException.class)\n    @ResponseBody\n    public BaseResponse handleException(ParentCategoryNotEnableException e) {\n        return new ErrorResponse(607, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = CategoryNameExistsException.class)\n    @ResponseStatus(HttpStatus.CONFLICT)\n    public BaseResponse handleException(CategoryNameExistsException e) {\n        return new ErrorResponse(409, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = CategoryHasDealException.class)\n    @ResponseBody\n    public BaseResponse handleException(CategoryHasDealException e) {\n        return new ErrorResponse(410, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = CategoryLevelException.class)\n    @ResponseBody\n    public BaseResponse handleException(CategoryLevelException e) {\n        return new ErrorResponse(703, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = CategoryIsDefaultExpenseException.class)\n    @ResponseBody\n    public BaseResponse handleException(CategoryIsDefaultExpenseException e) {\n        return new ErrorResponse(704, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = CategoryIsDefaultIncomeException.class)\n    @ResponseBody\n    public BaseResponse handleException(CategoryIsDefaultIncomeException e) {\n        return new ErrorResponse(705, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = CategoryMaxCountException.class)\n    @ResponseBody\n    public BaseResponse handleException(CategoryMaxCountException e) {\n        return new ErrorResponse(706, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryHasDealException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\n/*\n删除有账单的分类\n */\npublic class CategoryHasDealException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryIsDefaultExpenseException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\n/*\nCategoryIsDefaultExpenseException\n */\npublic class CategoryIsDefaultExpenseException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryIsDefaultIncomeException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\n/*\n账本的默认收入类别不能删除\n */\npublic class CategoryIsDefaultIncomeException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryLevelException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\n/*\n添加支出时，层级超过\n */\npublic class CategoryLevelException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryMaxCountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\npublic class CategoryMaxCountException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryNameExistsException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\n/*\n添加分类时，名称重复\n */\npublic class CategoryNameExistsException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryQueryRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class CategoryQueryRequest {\n\n    private String name;\n    private Boolean enable;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport org.springframework.stereotype.Repository;\nimport java.util.List;\nimport java.util.Optional;\n\n@Repository\npublic interface CategoryRepository extends HasBookRepository<Category> {\n\n    Optional<Category> findOneByBookAndNameAndParentAndType(Book book, String name, Category parent, Integer type);\n\n    List<Category> findAllByBookAndType(Book book, Integer type);\n\n    List<Category> findAllByBook(Book book);\n\n    List<Category> findAllByBookAndTypeAndEnable(Book book, Integer type, Boolean enable);\n\n    long countByBookAndType(Book book, Integer type);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.expense_category.ExpenseCategory;\nimport com.jiukuaitech.bookkeeping.user.income_category.IncomeCategory;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableSpec;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelationRepository;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.StringUtils;\n\nimport javax.annotation.Resource;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Service\npublic class CategoryService {\n\n    @Resource\n    private CategoryRepository categoryRepository;\n\n    @Resource\n    private CategoryRelationRepository categoryRelationRepository;\n\n    @Resource\n    private UserService userService;\n\n    @Value(\"${category.max.level}\")\n    private Integer maxLevel;\n\n    @Value(\"${category.max.count}\")\n    private Integer maxCount;\n\n    /*\n    删除分类需要检查：1. 有无支出；2. 有无账本的默认; 3. 有无子类\n     */\n    public boolean remove(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Category po = categoryRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        // 检查关联性，有账单关联的不能删除\n        if (categoryRelationRepository.countByCategory_id(id) > 0) {\n            throw new CategoryHasDealException();\n        }\n        if (po.equals(book.getDefaultExpenseCategory())) throw new CategoryIsDefaultExpenseException();\n        if (po.equals(book.getDefaultIncomeCategory())) throw new CategoryIsDefaultIncomeException();\n        categoryRepository.delete(po);\n        return true;\n    }\n\n    @Transactional\n    public boolean toggle(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Category po = categoryRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        List<Category> entityList = categoryRepository.findAll(BookNameNotesEnableSpec.isBook(po.getBook()));\n        List<Category> offSpring = po.getOffspring(entityList);\n        offSpring.add(po);\n        for (Category item : offSpring) {\n            if (item.equals(book.getDefaultExpenseCategory())) throw new CategoryIsDefaultExpenseException();\n            if (item.equals(book.getDefaultIncomeCategory())) throw new CategoryIsDefaultIncomeException();\n            item.setEnable(!po.getEnable());\n        }\n        categoryRepository.saveAll(offSpring);\n        return true;\n    }\n\n    public boolean add(Integer type, CategoryAddRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        Category parent = null;\n        if (request.getParentId() != null) {\n            parent = categoryRepository.findOneByBookAndId(book, request.getParentId()).orElseThrow(ItemNotFoundException::new);\n            if (!parent.getEnable()) {\n                throw new ParentCategoryNotEnableException();\n            }\n            if (parent.getLevel().equals(maxLevel-1)) {\n                throw new CategoryLevelException();\n            }\n        }\n        if (categoryRepository.countByBookAndType(book, type) >= maxCount) {\n            throw new CategoryMaxCountException();\n        }\n        if (categoryRepository.findOneByBookAndNameAndParentAndType(book, request.getName(), parent, type).isPresent()) {\n            throw new CategoryNameExistsException();\n        }\n        Category po = null;\n        if (type == 1) {\n            po = new ExpenseCategory();\n        } else {\n            po = new IncomeCategory();\n        }\n        po.setName(request.getName());\n        po.setNotes(request.getNotes());\n        po.setBook(book);\n        po.setParent(parent);\n        if (parent == null) po.setLevel(0);\n        else po.setLevel(parent.getLevel()+1);\n        categoryRepository.save(po);\n        return true;\n    }\n\n    public boolean update(Integer type, Integer id, CategoryUpdateRequest request, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Category po = categoryRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        Category parent = null;\n        if (request.getParentId() != null) {\n            parent = categoryRepository.findOneByBookAndId(book, request.getParentId()).orElseThrow(ItemNotFoundException::new);\n            if (!parent.getEnable()) {\n                throw new ParentCategoryNotEnableException();\n            }\n            if (parent.getLevel().equals(maxLevel-1)) {\n                throw new CategoryLevelException();\n            }\n        }\n        po.setParent(parent);\n        if (StringUtils.hasText(request.getName())) {\n            if(!request.getName().equals(po.getName())) {\n                if (categoryRepository.findOneByBookAndNameAndParentAndType(book, request.getName(), parent, type).isPresent()) {\n                    throw new CategoryNameExistsException();\n                }\n                po.setName(request.getName());\n            }\n        }\n        if (request.getNotes() != null) po.setNotes(request.getNotes());\n        if (parent == null) po.setLevel(0);\n        else po.setLevel(parent.getLevel()+1);\n        categoryRepository.save(po);\n        return true;\n    }\n\n    public List<CategorySimpleVO> getAllEnable(Integer type, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        List<Category> entityList = categoryRepository.findAllByBookAndTypeAndEnable(book, type, true);\n        return entityList.stream().map(CategorySimpleVO::fromEntity).collect(Collectors.toList());\n    }\n\n    public List<CategoryTreeVO> getAllTree(CategoryQueryRequest request, Integer type, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Specification<Category> specification = CategorySpec.buildSpecification(request, type, book);\n        List<Category> entityList = categoryRepository.findAll(specification);\n        return CategoryTreeVO.valueOfList(entityList);\n    }\n\n    public Page<CategorySimpleVO> query(Integer type, Pageable page, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Specification<Category> specification = CategorySpec.buildSpecification(null, type, book);\n        Page<Category> poPage = categoryRepository.findAll(specification, page);\n        return poPage.map(CategorySimpleVO::fromEntity);\n    }\n\n    public CategoryTreeVO get(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Category category = categoryRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        return CategoryTreeVO.valueOf(category);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategorySimpleVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class CategorySimpleVO {\n\n    private Integer id;\n    private String name;\n    private String notes;\n    private Boolean enable;\n    private Integer parentId;\n\n    public static CategorySimpleVO fromEntity(Category po) {\n        if (po == null) return null;\n        CategorySimpleVO vo =  new CategorySimpleVO();\n        vo.setId(po.getId());\n        vo.setName(po.getName());\n        vo.setNotes(po.getNotes());\n        vo.setEnable(po.getEnable());\n        vo.setParentId(po.getParent() != null ? po.getParent().getId() : 0);\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategorySpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableSpec;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.util.StringUtils;\n\npublic final class CategorySpec {\n\n    public static Specification<Category> typeEqual(Integer type) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Category_.type), type);\n    }\n\n    public static Specification<Category> buildSpecification(CategoryQueryRequest request, Integer type, Book book) {\n        Specification<Category> specification = BookNameNotesEnableSpec.isBook(book);\n        if (request != null) {\n            if (StringUtils.hasText(request.getName())) {\n                specification = specification.and(BookNameNotesEnableSpec.nameLike(request.getName()));\n            }\n            if (request.getEnable() != null) {\n                specification = specification.and(BookNameNotesEnableSpec.isEnable(request.getEnable()));\n            }\n        }\n        specification = specification.and(typeEqual(type));\n        return specification;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryTreeVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Getter\n@Setter\npublic class CategoryTreeVO {\n\n    private Integer id;\n    private String name;\n    private String notes;\n    private Boolean enable;\n    private List<CategoryTreeVO> children;\n    private Integer parentId;\n    private String parentName;\n\n    public static List<CategoryTreeVO> valueOfList(List<Category> categories) {\n        List<CategoryTreeVO> categoryTreeVOList = new ArrayList<>();\n        for (Category item : categories) {\n            if (item.getParent() == null) {\n                categoryTreeVOList.add(CategoryTreeVO.valueOf(item, categories));\n            }\n        }\n        return categoryTreeVOList;\n    }\n\n    public static CategoryTreeVO valueOf(Category category, List<Category> categories) {\n        CategoryTreeVO categoryTreeVO = new CategoryTreeVO();\n        categoryTreeVO.setId(category.getId());\n        categoryTreeVO.setName(category.getName());\n        categoryTreeVO.setNotes(category.getNotes());\n        categoryTreeVO.setEnable(category.getEnable());\n        categoryTreeVO.setParentId(category.getParent() == null ? null : category.getParent().getId());\n        categoryTreeVO.setParentName(category.getParent() == null ? null : category.getParent().getName());\n        if (!CollectionUtils.isEmpty(category.getChildren(categories))) {\n            for (Category item : category.getChildren(categories)) {\n                if (categoryTreeVO.getChildren() == null) {\n                    categoryTreeVO.setChildren(new ArrayList<>());\n                }\n                categoryTreeVO.getChildren().add(valueOf(item, categories));\n            }\n        }\n        return categoryTreeVO;\n    }\n\n    public static CategoryTreeVO valueOf(Category category) {\n        CategoryTreeVO categoryTreeVO = new CategoryTreeVO();\n        categoryTreeVO.setId(category.getId());\n        categoryTreeVO.setName(category.getName());\n        categoryTreeVO.setNotes(category.getNotes());\n        categoryTreeVO.setEnable(category.getEnable());\n        if (category.getParent() != null) categoryTreeVO.setParentId(category.getParent().getId());\n        if (category.getParent() != null) categoryTreeVO.setParentName(category.getParent().getName());\n        return categoryTreeVO;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/CategoryUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class CategoryUpdateRequest {\n\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    private Integer parentId;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/DefaultExpenseCategoryException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\npublic class DefaultExpenseCategoryException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/DefaultIncomeCategoryException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\npublic class DefaultIncomeCategoryException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category/ParentCategoryNotEnableException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category;\n\n/*\n添加子分类时，父分类不可用\n */\npublic class ParentCategoryNotEnableException extends RuntimeException {\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category_relation/CategoryRelation.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category_relation;\n\nimport com.jiukuaitech.bookkeeping.user.category.Category;\nimport com.jiukuaitech.bookkeeping.user.deal.Deal;\nimport com.jiukuaitech.bookkeeping.user.validation.AmountValidator;\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Entity\n//@Table(name = \"t_category_relation\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"deal_id\", \"category_id\"})})\n@Table(name = \"t_category_relation\")\n@Getter\n@Setter\npublic class CategoryRelation extends BaseEntity {\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"category_id\")\n    @NotNull\n    private Category category;\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"deal_id\")\n    @NotNull\n    private Deal deal;\n\n    @Column(nullable = false)\n    @NotNull\n    @AmountValidator\n    private BigDecimal amount; // 金额\n\n    @Column(nullable = false)\n    @NotNull\n    @AmountValidator\n    private BigDecimal convertedAmount; // 金额\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category_relation/CategoryRelationAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category_relation;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.category.Category;\nimport com.jiukuaitech.bookkeeping.user.deal.Deal;\nimport com.jiukuaitech.bookkeeping.user.validation.AmountValidator;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.AmountInvalidateException;\nimport com.jiukuaitech.bookkeeping.user.deal.CategoryConflictException;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n@Getter\n@Setter\npublic class CategoryRelationAddRequest {\n\n    @NotNull\n    private Integer categoryId;\n\n    @NotNull\n    @AmountValidator\n//    @Positive ///可以负数，代表退款\n    private BigDecimal amount;\n\n    private BigDecimal convertedAmount;\n\n    public static void checkCategory(List<CategoryRelationAddRequest> categories) {\n        Set<Integer> set = new HashSet<>();\n        for (CategoryRelationAddRequest item : categories) {\n            if (BigDecimal.ZERO.compareTo(item.getAmount()) == 0) {\n                throw new AmountInvalidateException();\n            }\n            if (item.getConvertedAmount() != null && BigDecimal.ZERO.compareTo(item.getConvertedAmount()) == 0) {\n                throw new AmountInvalidateException();\n            }\n            if (set.contains(item.getCategoryId())) {\n                throw new CategoryConflictException();\n            } else {\n                set.add(item.getCategoryId());\n            }\n        }\n    }\n\n    public CategoryRelation getRelation(Deal deal, Book book) {\n        CategoryRelation po = new CategoryRelation();\n        po.setAmount(amount);\n        if (deal.getAccount().getCurrencyCode().equals(book.getDefaultCurrencyCode())) {\n            po.setConvertedAmount(amount);\n        } else {\n            po.setConvertedAmount(convertedAmount);\n        }\n        po.setCategory(new Category(categoryId));\n        po.setDeal(deal);\n        return po;\n    }\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category_relation/CategoryRelationRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category_relation;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.List;\n\n\n@Repository\npublic interface CategoryRelationRepository extends BaseRepository<CategoryRelation, Integer> {\n\n    @Query(\"SELECT p1 FROM CategoryRelation p1 INNER JOIN p1.deal p2 WHERE p2.book = :book AND p2.status <> 2 AND p2.createTime BETWEEN :start AND :end\")\n    List<CategoryRelation> findByBookAndCreateTimeBetween(Book book, Long start, Long end);\n\n    Integer countByCategory_id(Integer id);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/category_relation/CategoryRelationVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.category_relation;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.math.BigDecimal;\n\n\n@Getter\n@Setter\npublic class CategoryRelationVOForList {\n\n    private Integer id;\n    private BigDecimal amount;\n    private BigDecimal convertedAmount;\n    private Integer categoryId;\n    private String categoryName;\n\n    public static CategoryRelationVOForList fromEntity(CategoryRelation po) {\n        if (po == null) return null;\n        CategoryRelationVOForList vo = new CategoryRelationVOForList();\n        vo.setId(po.getId());\n        vo.setAmount(po.getAmount());\n        vo.setConvertedAmount(po.getConvertedAmount());\n        vo.setCategoryId(po.getCategory().getId());\n        vo.setCategoryName(po.getCategory().getName());\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/checking_account/CheckingAccount.java",
    "content": "package com.jiukuaitech.bookkeeping.user.checking_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\n\nimport javax.persistence.DiscriminatorValue;\nimport javax.persistence.Entity;\n\n/**\n * 活期账户，例如银行卡的活期账户，支付宝余额，余额宝，微信零钱等，一般是可以直接用于支出，也可作为收入的入账账户。\n */\n@Entity\n@DiscriminatorValue(value = \"1\")\npublic class CheckingAccount extends Account {\n\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/checking_account/CheckingAccountController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.checking_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.account.AccountService;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport com.jiukuaitech.bookkeeping.user.account.AccountAddRequest;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/checking-accounts\")\npublic class CheckingAccountController extends BaseController {\n\n    @Resource\n    private CheckingAccountService checkingAccountService;\n\n    @Resource\n    private AccountService accountService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid AccountQueryRequest request,\n            @PageableDefault(sort = \"balance\", direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(checkingAccountService.query(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody AccountAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.add(1, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/sum\")\n    public BaseResponse handleSum(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(checkingAccountService.sum(userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/checking_account/CheckingAccountRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.checking_account;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface CheckingAccountRepository extends BaseRepository<CheckingAccount, Integer> {\n\n/*\n    @Query(\"SELECT COALESCE(SUM(p.balance), 0), \" +\n            \"COALESCE(SUM(p.expense), 0), \" +\n            \"COALESCE(SUM(p.income), 0), \" +\n            \"COALESCE(SUM(p.transferFrom), 0), \" +\n            \"COALESCE(SUM(p.transferTo), 0) \" +\n            \"FROM CheckingAccount p WHERE p.book = :book AND p.enable = true\")\n    List<BigDecimal[]> findSum(Book book);\n\n    @Query(\"SELECT COALESCE(SUM(p.balance), 0) FROM CheckingAccount p WHERE p.book = :book AND p.enable = true AND p.include = true\")\n    BigDecimal findSumBalance(Book book);\n*/\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/checking_account/CheckingAccountService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.checking_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.*;\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.List;\n\n@Service\npublic class CheckingAccountService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private CheckingAccountRepository checkingAccountRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    public Page<AccountVOForExtend> query(AccountQueryRequest request, Pageable page, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Specification<CheckingAccount> specification = AccountSpec.buildSpecification(request, group);\n        Page<CheckingAccount> poPage = checkingAccountRepository.findAll(specification, page);\n        Page<AccountVOForExtend> voPage = poPage.map(AccountVOForExtend::fromEntity);\n        voPage.map(vo->{\n            vo.setConvertedBalance(currencyService.convert(vo.getBalance(), vo.getCurrencyCode(), group.getDefaultCurrencyCode()));\n            return vo;\n        });\n        return voPage;\n    }\n\n    public AccountSumVO sum(Integer userSignInId) {\n        AccountSumVO vo = new AccountSumVO();\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<CheckingAccount> accounts = checkingAccountRepository.findAll(AccountSpec.inGroupAndEnable(group));\n        BigDecimal balance = BigDecimal.ZERO;\n        for (int i = 0; i < accounts.size(); i++) {\n            Account account = accounts.get(i);\n            balance = balance.add(currencyService.convert(account.getBalance(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n        }\n        vo.setBalance(balance);\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/credit_account/CreditAccount.java",
    "content": "package com.jiukuaitech.bookkeeping.user.credit_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.validation.BillDayValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.CreditLimitValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\nimport javax.persistence.Column;\nimport javax.persistence.DiscriminatorValue;\nimport javax.persistence.Entity;\nimport javax.validation.constraints.NotNull;\n\n/**\n * 信用账户，例如信用卡，花呗，白条等。一般可以直接用于支出，但不能作为收入的入账账户。\n */\n@Entity\n@DiscriminatorValue(value = \"2\")\n@Getter\n@Setter\npublic class CreditAccount extends Account {\n\n    // 非空\n    @Column(name = \"credit_limit\")\n    @NotNull\n    @CreditLimitValidator\n    private BigDecimal limit; // 信用额度\n\n    @BillDayValidator\n    private Integer billDay; // 每月多少号是账单日\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/credit_account/CreditAccountAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.credit_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountAddRequest;\nimport com.jiukuaitech.bookkeeping.user.validation.BillDayValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.CreditLimitValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class CreditAccountAddRequest extends AccountAddRequest {\n\n    @NotNull\n    @CreditLimitValidator\n    private BigDecimal limit; // 信用额度\n\n    @BillDayValidator\n    private Integer billDay; // 每月多少号是账单日\n\n    public void copyPrimitive(CreditAccount po) {\n        super.copyPrimitive(po);\n        po.setLimit(limit);\n        po.setBillDay(billDay);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/credit_account/CreditAccountController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.credit_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountService;\nimport com.jiukuaitech.bookkeeping.user.account.AccountQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n\n@RestController\n@RequestMapping(\"/credit-accounts\")\npublic class CreditAccountController extends BaseController {\n\n    @Resource\n    private CreditAccountService creditAccountService;\n\n    @Resource\n    private AccountService accountService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid AccountQueryRequest request,\n            @PageableDefault(sort = \"balance\", direction = Sort.Direction.ASC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(creditAccountService.query(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody CreditAccountAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.add(2, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/sum\")\n    public BaseResponse handleSum(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(creditAccountService.sum(userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/credit_account/CreditAccountRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.credit_account;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface CreditAccountRepository extends BaseRepository<CreditAccount, Integer> {\n\n/*\n    @Query(\"SELECT COALESCE(SUM(p.balance), 0), \" +\n            \"COALESCE(SUM(p.expense), 0), \" +\n            \"COALESCE(SUM(p.income), 0), \" +\n            \"COALESCE(SUM(p.transferFrom), 0), \" +\n            \"COALESCE(SUM(p.transferTo), 0), \" +\n            \"COALESCE(SUM(p.limit), 0) \" +\n            \"FROM CreditAccount p WHERE p.book = :book AND p.enable = true\")\n    List<BigDecimal[]> findSum(Book book);\n\n    @Query(\"SELECT COALESCE(SUM(p.balance), 0) FROM CreditAccount p WHERE p.book = :book AND p.enable = true AND p.include = true\")\n    BigDecimal findSumBalance(Book book);\n*/\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/credit_account/CreditAccountService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.credit_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.account.AccountSpec;\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.List;\n\n@Service\npublic class CreditAccountService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private CreditAccountRepository creditAccountRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    public Page<CreditAccountVOForList> query(AccountQueryRequest request, Pageable page, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Specification<CreditAccount> specification = AccountSpec.buildSpecification(request, group);\n        Page<CreditAccount> poPage = creditAccountRepository.findAll(specification, page);\n        Page<CreditAccountVOForList> voPage = poPage.map(CreditAccountVOForList::fromEntity);\n        voPage.map(vo->{\n            vo.setConvertedBalance(currencyService.convert(vo.getBalance(), vo.getCurrencyCode(), group.getDefaultCurrencyCode()));\n            return vo;\n        });\n        return voPage;\n    }\n\n    public CreditAccountSumVO sum(Integer userSignInId) {\n        CreditAccountSumVO vo = new CreditAccountSumVO();\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<CreditAccount> accounts = creditAccountRepository.findAll(AccountSpec.inGroupAndEnable(group));\n        BigDecimal balance = BigDecimal.ZERO;\n        BigDecimal limit = BigDecimal.ZERO;\n        for (int i = 0; i < accounts.size(); i++) {\n            CreditAccount account = accounts.get(i);\n            balance = balance.add(currencyService.convert(account.getBalance(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n            limit = limit.add(currencyService.convert(account.getLimit(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n        }\n        vo.setBalance(balance);\n        vo.setLimit(limit);\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/credit_account/CreditAccountSumVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.credit_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountSumVO;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class CreditAccountSumVO extends AccountSumVO {\n\n    private BigDecimal limit;\n\n    public BigDecimal getRemainLimit() {\n        return limit.add(getBalance());\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/credit_account/CreditAccountVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.credit_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountVOForExtend;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class CreditAccountVOForList extends AccountVOForExtend {\n\n    private BigDecimal limit;\n    private Integer billDay;\n\n    public static CreditAccountVOForList fromEntity(CreditAccount po) {\n        CreditAccountVOForList vo = new CreditAccountVOForList();\n        vo.setValue(po);\n        vo.setLimit(po.getLimit());\n        vo.setBillDay(po.getBillDay());\n        return vo;\n    }\n\n    public BigDecimal getRemainLimit() {\n        return limit.add(getBalance());\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/currency/Currency.java",
    "content": "package com.jiukuaitech.bookkeeping.user.currency;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.Column;\nimport javax.persistence.Entity;\nimport javax.persistence.Table;\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Entity\n@Table(name = \"t_currency\")\n@Getter\n@Setter\npublic class Currency extends BaseEntity {\n\n    @Column(length = 8, nullable = false, unique = true)\n    @NotNull\n    private String code;\n\n    @Column(length = 128, nullable = false)\n    @NotNull\n    private String description;\n\n    @Column(nullable = false) //最多9亿\n    @NotNull\n    private BigDecimal rate;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/currency/CurrencyController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.currency;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\n\n@RestController\n@RequestMapping(\"/currency\")\npublic class CurrencyController extends BaseController {\n\n    @Resource\n    private CurrencyService currencyService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/all\")\n    public BaseResponse handleAll() {\n        return new DataResponse<>(currencyService.getAll());\n    }\n\n}"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/currency/CurrencyRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.currency;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.Optional;\n\n@Repository\npublic interface CurrencyRepository extends BaseRepository<Currency, Integer> {\n\n    Optional<Currency> findOneByCode(String code);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/currency/CurrencyService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.currency;\n\nimport com.jiukuaitech.bookkeeping.user.exception.InputNotValidException;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Service\npublic class CurrencyService {\n\n    @Resource\n    private CurrencyRepository currencyRepository;\n\n    public List<Currency> getAll() {\n        return currencyRepository.findAll();\n    }\n\n    public void checkCode(String code) {\n        if (code == null) return;;\n        List<Currency> currencyList = getAll();\n        List<String> currencyCodeList = currencyList.stream().map(Currency::getCode).collect(Collectors.toList());\n        if (!currencyCodeList.contains(code)) {\n            throw new InputNotValidException();\n        }\n    }\n\n    // TODO 定时任务，数据存入缓存\n    public BigDecimal convert(String fromCode, String toCode) {\n        Currency fromCurrency = currencyRepository.findOneByCode(fromCode).orElseThrow();\n        Currency toCurrency = currencyRepository.findOneByCode(toCode).orElseThrow();\n        BigDecimal fromRate = fromCurrency.getRate();\n        BigDecimal toRate = toCurrency.getRate();\n        return toRate.divide(fromRate, 2, RoundingMode.CEILING);\n    }\n\n    public BigDecimal convert(BigDecimal amount, String fromCode, String toCode) {\n        if (fromCode.equals(toCode)) {\n            return amount;\n        }\n        return amount.multiply(convert(fromCode, toCode)).setScale(2, RoundingMode.CEILING);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/dashboard/AssetOverviewVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.dashboard;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class AssetOverviewVO {\n\n    private BigDecimal asset;//资产\n    private BigDecimal debt;//负债\n\n    public BigDecimal getNetWorth() {\n        return asset.subtract(debt);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/dashboard/DashboardController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.dashboard;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\n\n@RestController\n@RequestMapping(\"/dashboard\")\npublic class DashboardController  extends BaseController {\n\n    @Resource\n    private DashboardService dashboardService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"asset-overview\")\n    public BaseResponse handleAssetOverview(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(dashboardService.assetOverview(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"expense-income-table\")\n    public BaseResponse handleTable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(dashboardService.expenseIncomeTable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"expense-trend\")\n    public BaseResponse handleExpenseTrend(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(dashboardService.expenseTrend(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"income-trend\")\n    public BaseResponse handleIncomeTrend(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(dashboardService.incomeTrend(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"expense-category\")\n    public BaseResponse handleExpenseCategory(@RequestParam Long start, @RequestParam Long end, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(dashboardService.expenseCategory(start, end, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"income-category\")\n    public BaseResponse handleIncomeCategory(@RequestParam Long start, @RequestParam Long end, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(dashboardService.incomeCategory(start, end, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/dashboard/DashboardService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.dashboard;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.account.AccountRepository;\nimport com.jiukuaitech.bookkeeping.user.account.AccountSpec;\nimport com.jiukuaitech.bookkeeping.user.asset_account.AssetAccount;\nimport com.jiukuaitech.bookkeeping.user.asset_account.AssetAccountRepository;\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.category.Category;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryRepository;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelation;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelationRepository;\nimport com.jiukuaitech.bookkeeping.user.checking_account.CheckingAccount;\nimport com.jiukuaitech.bookkeeping.user.checking_account.CheckingAccountRepository;\nimport com.jiukuaitech.bookkeeping.user.credit_account.CreditAccount;\nimport com.jiukuaitech.bookkeeping.user.credit_account.CreditAccountRepository;\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.debt_account.DebtAccount;\nimport com.jiukuaitech.bookkeeping.user.debt_account.DebtAccountRepository;\nimport com.jiukuaitech.bookkeeping.user.expense.ExpenseRepository;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.income.IncomeRepository;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.reports.ChartVO;\nimport com.jiukuaitech.bookkeeping.user.utils.CalendarUtils;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n@Service\npublic class DashboardService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private AccountRepository accountRepository;\n\n    @Resource\n    private CheckingAccountRepository checkingAccountRepository;\n\n    @Resource\n    private CreditAccountRepository creditAccountRepository;\n\n    @Resource\n    private DebtAccountRepository debtAccountRepository;\n\n    @Resource\n    private AssetAccountRepository assetAccountRepository;\n\n    @Resource\n    private ExpenseRepository expenseRepository;\n\n    @Resource\n    private IncomeRepository incomeRepository;\n\n    @Resource\n    private CategoryRepository categoryRepository;\n\n    @Resource\n    private CategoryRelationRepository categoryRelationRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    public AssetOverviewVO assetOverview(Integer userSignInId) {\n        AssetOverviewVO vo = new AssetOverviewVO();\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<CheckingAccount> checkingAccounts = checkingAccountRepository.findAll(AccountSpec.inGroupAndInclude(group));\n        List<AssetAccount> assetAccounts = assetAccountRepository.findAll(AccountSpec.inGroupAndInclude(group));\n        BigDecimal assetBalance = BigDecimal.ZERO;\n        for (int i = 0; i < checkingAccounts.size(); i++) {\n            Account account = checkingAccounts.get(i);\n            assetBalance = assetBalance.add(currencyService.convert(account.getBalance(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n        }\n        for (int i = 0; i < assetAccounts.size(); i++) {\n            Account account = assetAccounts.get(i);\n            assetBalance = assetBalance.add(currencyService.convert(account.getBalance(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n        }\n        vo.setAsset(assetBalance);\n        List<CreditAccount> creditAccounts = creditAccountRepository.findAll(AccountSpec.inGroupAndInclude(group));\n        List<DebtAccount> debtAccounts = debtAccountRepository.findAll(AccountSpec.inGroupAndInclude(group));\n        BigDecimal debtBalance = BigDecimal.ZERO;\n        for (int i = 0; i < creditAccounts.size(); i++) {\n            Account account = creditAccounts.get(i);\n            debtBalance = debtBalance.add(currencyService.convert(account.getBalance(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n        }\n        for (int i = 0; i < debtAccounts.size(); i++) {\n            Account account = debtAccounts.get(i);\n            debtBalance = debtBalance.add(currencyService.convert(account.getBalance(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n        }\n        vo.setDebt(debtBalance.negate());\n        return vo;\n    }\n\n    public List<List<BigDecimal>> expenseIncomeTable(Integer userSignInId) {\n        List<List<BigDecimal>> result = new ArrayList<>();\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n\n        Long[] thisWeek = CalendarUtils.getThisWeek();\n        Long[] thisMonth = CalendarUtils.getThisMonth();\n        Long[] thisYear = CalendarUtils.getThisYear();\n        Long[] lastYear = CalendarUtils.getLastYear();\n        Long[] in7Days = CalendarUtils.getIn7Days();\n        Long[] in30Days = CalendarUtils.getIn30Days();\n        Long[] in1Year = CalendarUtils.getIn1Year();\n\n        {\n            List<BigDecimal> list = new ArrayList<>();\n            list.add(expenseRepository.findSumAmount(book, thisWeek[0], thisWeek[1]));\n            list.add(expenseRepository.findSumAmount(book, thisMonth[0], thisMonth[1]));\n            list.add(expenseRepository.findSumAmount(book, thisYear[0], thisYear[1]));\n            list.add(expenseRepository.findSumAmount(book, lastYear[0], lastYear[1]));\n            list.add(expenseRepository.findSumAmount(book, in7Days[0], in7Days[1]));\n            list.add(expenseRepository.findSumAmount(book, in30Days[0], in30Days[1]));\n            list.add(expenseRepository.findSumAmount(book, in1Year[0], in1Year[1]));\n            result.add(list);\n        }\n\n        {\n            List<BigDecimal> list = new ArrayList<>();\n            list.add(incomeRepository.findSumAmount(book, thisWeek[0], thisWeek[1]));\n            list.add(incomeRepository.findSumAmount(book, thisMonth[0], thisMonth[1]));\n            list.add(incomeRepository.findSumAmount(book, thisYear[0], thisYear[1]));\n            list.add(incomeRepository.findSumAmount(book, lastYear[0], lastYear[1]));\n            list.add(incomeRepository.findSumAmount(book, in7Days[0], in7Days[1]));\n            list.add(incomeRepository.findSumAmount(book, in30Days[0], in30Days[1]));\n            list.add(incomeRepository.findSumAmount(book, in1Year[0], in1Year[1]));\n            result.add(list);\n        }\n\n        {\n            List<BigDecimal> list = new ArrayList<>();\n            list.add(BigDecimal.valueOf(expenseRepository.findCount(book, thisWeek[0], thisWeek[1])));\n            list.add(BigDecimal.valueOf(expenseRepository.findCount(book, thisMonth[0], thisMonth[1])));\n            list.add(BigDecimal.valueOf(expenseRepository.findCount(book, thisYear[0], thisYear[1])));\n            list.add(BigDecimal.valueOf(expenseRepository.findCount(book, lastYear[0], lastYear[1])));\n            list.add(BigDecimal.valueOf(expenseRepository.findCount(book, in7Days[0], in7Days[1])));\n            list.add(BigDecimal.valueOf(expenseRepository.findCount(book, in30Days[0], in30Days[1])));\n            list.add(BigDecimal.valueOf(expenseRepository.findCount(book, in1Year[0], in1Year[1])));\n            result.add(list);\n        }\n\n        {\n            List<BigDecimal> list = new ArrayList<>();\n            list.add(BigDecimal.valueOf(incomeRepository.findCount(book, thisWeek[0], thisWeek[1])));\n            list.add(BigDecimal.valueOf(incomeRepository.findCount(book, thisMonth[0], thisMonth[1])));\n            list.add(BigDecimal.valueOf(incomeRepository.findCount(book, thisYear[0], thisYear[1])));\n            list.add(BigDecimal.valueOf(incomeRepository.findCount(book, lastYear[0], lastYear[1])));\n            list.add(BigDecimal.valueOf(incomeRepository.findCount(book, in7Days[0], in7Days[1])));\n            list.add(BigDecimal.valueOf(incomeRepository.findCount(book, in30Days[0], in30Days[1])));\n            list.add(BigDecimal.valueOf(incomeRepository.findCount(book, in1Year[0], in1Year[1])));\n            result.add(list);\n        }\n\n        return result;\n    }\n\n    public List<ChartVO> expenseTrend(Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        List<Calendar[]> months = CalendarUtils.getMonths(Calendar.getInstance().get(Calendar.YEAR));\n        months.forEach(i -> {\n            ChartVO vo = new ChartVO();\n            vo.setX(new SimpleDateFormat(\"yyyy.MM\").format(i[0].getTime()));\n            vo.setY(expenseRepository.findSumAmount(book, i[0].getTimeInMillis(), i[1].getTimeInMillis()));\n            result.add(vo);\n        });\n        return result;\n    }\n\n    public List<ChartVO> incomeTrend(Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        List<Calendar[]> months = CalendarUtils.getMonths(Calendar.getInstance().get(Calendar.YEAR));\n        months.forEach(i -> {\n            ChartVO vo = new ChartVO();\n            vo.setX(new SimpleDateFormat(\"yyyy.MM\").format(i[0].getTime()));\n            vo.setY(incomeRepository.findSumAmount(book, i[0].getTimeInMillis(), i[1].getTimeInMillis()));\n            result.add(vo);\n        });\n        return result;\n    }\n\n    public List<ChartVO> expenseCategory(Long start, Long end, Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        List<Category> categories = categoryRepository.findAllByBookAndType(book, 1);\n        List<Category> rootCategories = categories.stream().filter(i->i.getLevel() == 0).collect(Collectors.toList());\n        List<CategoryRelation> relations = categoryRelationRepository.findByBookAndCreateTimeBetween(book, start, end);\n        rootCategories.forEach(i -> {\n            ChartVO vo = new ChartVO();\n            vo.setX(i.getName());\n            List<Integer> offSpringCategoryIds = i.getOffspring(categories).stream().map(BaseEntity::getId).collect(Collectors.toList());\n            offSpringCategoryIds.add(i.getId());\n            vo.setY(relations.stream().filter(j -> offSpringCategoryIds.contains(j.getCategory().getId())).map(CategoryRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            if (vo.getY().compareTo(BigDecimal.ZERO) > 0) {\n                result.add(vo);\n            }\n        });\n        return result;\n    }\n\n    public List<ChartVO> incomeCategory(Long start, Long end, Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        List<Category> categories = categoryRepository.findAllByBookAndType(book, 2);\n        List<Category> rootCategories = categories.stream().filter(i->i.getLevel() == 0).collect(Collectors.toList());\n        List<CategoryRelation> incomeFroms = categoryRelationRepository.findByBookAndCreateTimeBetween(book, start, end);\n        rootCategories.forEach(i -> {\n            ChartVO vo = new ChartVO();\n            vo.setX(i.getName());\n            List<Integer> offSpringCategoryIds = i.getOffspring(categories).stream().map(BaseEntity::getId).collect(Collectors.toList());\n            offSpringCategoryIds.add(i.getId());\n            vo.setY(incomeFroms.stream().filter(j -> offSpringCategoryIds.contains(j.getCategory().getId())).map(CategoryRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            if (vo.getY().compareTo(BigDecimal.ZERO) > 0) {\n                result.add(vo);\n            }\n        });\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/CategoryConflictException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\n/*\n添加支出或收入时，选择的分类有重复\n */\npublic class CategoryConflictException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/Deal.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport com.jiukuaitech.bookkeeping.user.payee.Payee;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelation;\nimport com.jiukuaitech.bookkeeping.user.transaction.Transaction;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@Entity\n@Getter\n@Setter\npublic class Deal extends Transaction {\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    private Payee payee;\n\n    @OneToMany(mappedBy = \"deal\", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)\n//    @NotEmpty\n    private Set<CategoryRelation> categories = new HashSet<>();\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.category.Category;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelationAddRequest;\nimport com.jiukuaitech.bookkeeping.user.exception.InputNotValidException;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionAddRequest;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Getter\n@Setter\npublic class DealAddRequest extends TransactionAddRequest {\n\n    @NotNull\n    private Integer accountId;\n\n    private Integer payeeId;\n\n    @NotEmpty\n    @Valid\n    private List<CategoryRelationAddRequest> categories;\n\n    public void copyCategories(Deal po, List<Category> categories, Book book) {\n        getCategories().forEach(i-> {\n            List<Integer> categoryIds = categories.stream().map(BaseEntity::getId).collect(Collectors.toList());\n            if (categoryIds.contains(i.getCategoryId())) {\n                po.getCategories().add(i.getRelation(po, book));\n            } else {\n                throw new InputNotValidException();\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.refund.RefundService;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\n\n@RestController\n@RequestMapping(\"/deals\")\npublic class DealController extends BaseController {\n\n    @Resource\n    private RefundService refundService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/{id}/refunds\")\n    public BaseResponse handleRefunds(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(refundService.getRefunds(id, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class DealExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = CategoryConflictException.class)\n    @ResponseBody\n    public BaseResponse handleException(CategoryConflictException e) {\n        return new ErrorResponse(702, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealQueryResultVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.data.domain.Page;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class DealQueryResultVO {\n\n    private Page<DealVOForList> result;\n    private BigDecimal total;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface DealRepository extends HasBookRepository<Deal> {\n\n    Integer countByPayee_id(Integer payeeId);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.AccountInvalidateException;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.StatusNotValidateException;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryRepository;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelationAddRequest;\nimport com.jiukuaitech.bookkeeping.user.expense.Expense;\nimport com.jiukuaitech.bookkeeping.user.flow_images.FlowImageRepository;\nimport com.jiukuaitech.bookkeeping.user.income.Income;\nimport com.jiukuaitech.bookkeeping.user.payee.Payee;\nimport com.jiukuaitech.bookkeeping.user.payee.PayeeRepository;\nimport com.jiukuaitech.bookkeeping.user.refund.Refund;\nimport com.jiukuaitech.bookkeeping.user.refund.RefundRepository;\nimport com.jiukuaitech.bookkeeping.user.tag.TagRepository;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.account.AccountRepository;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.AmountInvalidateException;\nimport com.jiukuaitech.bookkeeping.user.exception.InputNotValidException;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLog;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLogRepository;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLogService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.time.Instant;\n\n@Service\npublic class DealService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private AccountRepository accountRepository;\n\n    @Resource\n    private DealRepository dealRepository;\n\n    @Resource\n    private FlowImageRepository dealImageRepository;\n\n    @Resource\n    private RefundRepository refundRepository;\n\n    @Resource\n    private PayeeRepository payeeRepository;\n\n    @Resource\n    private TagRepository tagRepository;\n\n    @Resource\n    private CategoryRepository categoryRepository;\n\n    @Resource\n    private UserActionLogRepository userActionLogRepository;\n\n    @Resource\n    private UserActionLogService userActionLogService;\n\n    @Transactional\n    // 正常添加amount只能为正，退款的添加amount只能为负。\n    public Deal add(Integer type, DealAddRequest request, Integer userSignInId, boolean refundFlag) {\n        //检查Category有没有重复\n        CategoryRelationAddRequest.checkCategory(request.getCategories());\n        User user = userService.getUser(userSignInId);\n        // 不能频繁添加，防止用户恶意操作\n        userActionLogService.check(user);\n        Book book = user.getDefaultBook();\n        Deal po = null;\n        if (type == 1) {\n            po = new Expense();\n        } else if (type == 2) {\n            po = new Income();\n        }\n        BigDecimal amount = request.getCategories().stream().map(CategoryRelationAddRequest::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);\n        if (refundFlag && amount.compareTo(BigDecimal.ZERO) > 0) {\n            throw new AmountInvalidateException();\n        }\n        if (!refundFlag && amount.compareTo(BigDecimal.ZERO) < 0) {\n            throw new AmountInvalidateException();\n        }\n        po.setAmount(amount);\n        Account account = accountRepository.findOneByGroupAndId(user.getDefaultGroup(), request.getAccountId()).orElseThrow(AccountInvalidateException::new);\n        po.setAccount(account);\n        if (account.getCurrencyCode().equals(book.getDefaultCurrencyCode())) {\n            po.setConvertedAmount(amount);\n        } else {\n            BigDecimal convertedAmount = request.getCategories().stream().map(CategoryRelationAddRequest::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);\n            if (refundFlag && convertedAmount.compareTo(BigDecimal.ZERO) > 0) {\n                throw new AmountInvalidateException();\n            }\n            if (!refundFlag && convertedAmount.compareTo(BigDecimal.ZERO) < 0) {\n                throw new AmountInvalidateException();\n            }\n            po.setConvertedAmount(convertedAmount);\n        }\n        po.setCreator(user);\n        po.setBook(user.getDefaultBook());\n        po.setGroup(user.getDefaultGroup());\n        if (request.getPayeeId() != null) {\n            Payee payee = payeeRepository.findOneByBookAndId(book, request.getPayeeId()).orElseThrow(InputNotValidException::new);\n            po.setPayee(payee);\n        }\n        request.copyTags(po, tagRepository.findByBookAndEnable(book, true));\n        request.copyCategories(po, categoryRepository.findAllByBook(book), book);\n        request.copyPrimitive(po);\n        dealRepository.save(po);\n        confirmBalance(po, type);\n        userActionLogRepository.save(new UserActionLog(user, 1, Instant.now().toEpochMilli()));\n        return po;\n    }\n\n    @Transactional\n    // 已经退款的不能修改\n    public boolean update(Integer type, Integer id, DealUpdateRequest request, Integer userSignInId) {\n        //检查Category有没有重复\n        CategoryRelationAddRequest.checkCategory(request.getCategories());\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        Deal po = dealRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        BigDecimal newAmount = request.getCategories().stream().map(CategoryRelationAddRequest::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);\n        //正数不能改负数，负数不能改正数\n        // 不能改变正负符号，也就是不能改变是否是退款的情况。\n        if (po.getAmount().compareTo(BigDecimal.ZERO) > 0 && newAmount.compareTo(BigDecimal.ZERO) < 0) {\n            throw new AmountInvalidateException();\n        }\n        if (po.getAmount().compareTo(BigDecimal.ZERO) < 0 && newAmount.compareTo(BigDecimal.ZERO) > 0) {\n            throw new AmountInvalidateException();\n        }\n        boolean refundFlag = false;//是否更新账户金额\n        if (po.getAmount().compareTo(newAmount) != 0) {\n            refundFlag = true;\n            // 不然更新金额，标签对应的金额不更新\n            // 只有更新金额才自动更新标签的金额，更新分类不更新标签的金额\n            po.getTags().forEach(i->i.setAmount(newAmount));\n        } else if (request.getAccountId() != null && !request.getAccountId().equals(po.getAccount().getId())) {\n            refundFlag = true;\n        }\n        if (refundFlag) {\n            refundBalance(po, type);\n        }\n        Account account = accountRepository.findOneByGroupAndId(user.getDefaultGroup(), request.getAccountId()).orElseThrow(AccountInvalidateException::new);\n        po.setAccount(account);\n        po.setAmount(newAmount);\n        if (account.getCurrencyCode().equals(book.getDefaultCurrencyCode())) {\n            po.setConvertedAmount(newAmount);\n        } else {\n            BigDecimal newConvertedAmount = request.getCategories().stream().map(CategoryRelationAddRequest::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add);\n            if (po.getConvertedAmount().compareTo(BigDecimal.ZERO) > 0 && newConvertedAmount.compareTo(BigDecimal.ZERO) < 0) {\n                throw new AmountInvalidateException();\n            }\n            if (po.getConvertedAmount().compareTo(BigDecimal.ZERO) < 0 && newConvertedAmount.compareTo(BigDecimal.ZERO) > 0) {\n                throw new AmountInvalidateException();\n            }\n            po.setConvertedAmount(newConvertedAmount);\n        }\n        if (request.getPayeeId() != null) {\n            Payee payee = payeeRepository.findOneByBookAndId(book, request.getPayeeId()).orElseThrow(InputNotValidException::new);\n            po.setPayee(payee);\n        } else {\n            po.setPayee(null);\n        }\n        request.updateTags(po, tagRepository.findByBookAndEnable(book, true));\n        request.updateCategories(po, categoryRepository.findAllByBook(book), book);\n        request.updatePrimitive(po);\n        dealRepository.save(po);\n        if (refundFlag) {\n           confirmBalance(po, type);\n        }\n        return true;\n    }\n\n    @Transactional\n    // 只有状态是正常的才能退款\n    public boolean refund(Integer type, Integer id, DealAddRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        Deal deal = dealRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        // 如果新增状态值可能有问题\n        if (deal.getStatus() == 2 || deal.getAmount().compareTo(BigDecimal.ZERO) < 0) throw new StatusNotValidateException();\n        Deal refundDeal = add(type, request, userSignInId, true);\n        // 状态变成已退款\n        deal.setStatus(3);\n        Refund refund = new Refund(deal, refundDeal);\n        refundRepository.save(refund);\n        return true;\n    }\n\n    // 1. 退回账户余额变更，2. 删除图片关联以及文件，3. 删除关联category relation，4. 删除关联tag relation 5. 处理关联的refund，最后删除自己。\n    // 已经退款的不能删除，必须先删除对应的退款记录\n    @Transactional\n    public boolean remove(Integer type, Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Deal po = dealRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        if (po.getStatus() == 3) throw new StatusNotValidateException();\n        refundBalance(po, type);\n        // 处理图片\n        po.getImages().forEach(i -> {\n            i.setFlow(null);\n            dealImageRepository.save(i);\n        });\n        // category relation tag relation自动处理\n        // 处理关联的refund\n        if (po.getAmount().compareTo(BigDecimal.ZERO) < 0) {//小于0说明是退款记录\n            Refund refund = refundRepository.findByRefund(po).orElseThrow(ItemNotFoundException::new);\n            Deal deal = refund.getDeal();\n            deal.setStatus(1);\n            dealRepository.save(deal);\n            refundRepository.delete(refund);\n        }\n        dealRepository.delete(po);\n        return true;\n    }\n\n    @Transactional\n    public boolean confirm(Integer type, Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Deal po = dealRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        if (po.getStatus() != 2) {\n            throw new StatusNotValidateException();\n        }\n        po.setStatus(1);\n        confirmBalance(po, type);\n        dealRepository.save(po);\n        return true;\n    }\n\n    // 余额确认，包括账户扣款，账户统计，账户日志记录。\n    private void confirmBalance(Deal deal, Integer type) {\n        if (deal.getStatus() == 2) return;//未确认，不更新账户余额\n        Account account = deal.getAccount();\n        if (type == 1) {\n            account.setBalance(account.getBalance().subtract(deal.getAmount()));\n        } else if (type == 2) {\n            account.setBalance(account.getBalance().add(deal.getAmount()));\n        }\n        accountRepository.save(account);\n    }\n\n    // 退款\n    private void refundBalance(Deal deal, Integer type) {\n        if (deal.getStatus() == 2) return;//未确认，不更新账户余额\n        Account account = deal.getAccount();\n        if (type == 1) {\n            account.setBalance(account.getBalance().add(deal.getAmount()));\n        } else if (type == 2) {\n            account.setBalance(account.getBalance().subtract(deal.getAmount()));\n        }\n        accountRepository.save(account);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelation;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.payee.Payee;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelation_;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionSpec;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.util.CollectionUtils;\n\nimport javax.persistence.criteria.CriteriaBuilder;\nimport javax.persistence.criteria.Join;\nimport java.util.Set;\n\npublic final class DealSpec {\n\n    public static<T extends Deal> Specification<T> payeeIn(Set<Integer> payees) {\n        return (root, query, criteriaBuilder) -> {\n            CriteriaBuilder.In<Payee> in = criteriaBuilder.in(root.get(Deal_.payee));\n            payees.forEach(i -> in.value(new Payee(i)));\n            return in;\n        };\n    }\n\n    public static<T extends Deal> Specification<T> categoriesIn(Set<Integer> categories) {\n        return (root, query, criteriaBuilder) -> {\n            Join<T, CategoryRelation> join = root.join(Deal_.categories);\n            return join.get(CategoryRelation_.category).in(categories);\n        };\n    }\n\n    public static<T extends Deal> Specification<T> buildSpecification(BalanceFlowQueryRequest request, Group group) {\n        Specification<T> specification = TransactionSpec.buildSpecification(request, group);\n        if (!CollectionUtils.isEmpty(request.getPayees())) {\n            specification = specification.and(payeeIn(request.getPayees()));\n        }\n        if (!CollectionUtils.isEmpty(request.getCategories())) {\n            specification = specification.and(categoriesIn(request.getCategories()));\n        }\n        return specification;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.category.Category;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelationAddRequest;\nimport com.jiukuaitech.bookkeeping.user.exception.InputNotValidException;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionUpdateRequest;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotEmpty;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n\n@Getter\n@Setter\npublic class DealUpdateRequest extends TransactionUpdateRequest {\n\n    private Integer accountId;\n\n    private Integer payeeId;\n\n    @NotEmpty\n    @Valid\n    private List<CategoryRelationAddRequest> categories;\n\n    public void updateCategories(Deal po, List<Category> categories, Book book) {\n        po.getCategories().clear();\n        getCategories().forEach(i-> {\n            List<Integer> categoryIds = categories.stream().map(BaseEntity::getId).collect(Collectors.toList());\n            if (categoryIds.contains(i.getCategoryId())) {\n                po.getCategories().add(i.getRelation(po, book));\n            } else {\n                throw new InputNotValidException();\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/deal/DealVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.deal;\n\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelationVOForList;\nimport com.jiukuaitech.bookkeeping.user.response.HasNameVO;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionVOForList;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n@Getter\n@Setter\npublic class DealVOForList extends TransactionVOForList {\n\n    private HasNameVO payee;\n    private String categoryName;\n    private Set<CategoryRelationVOForList> categories = new HashSet<>();\n\n    public void setValue(Deal po) {\n        super.setValue(po);\n        if (po.getPayee() != null) setPayee(new HasNameVO(po.getPayee().getId(), po.getPayee().getName()));\n        setCategoryName(po.getCategories().stream().map(i -> i.getCategory().getName() + \"(\" + i.getAmount().stripTrailingZeros().toPlainString()+\")\").collect(Collectors.joining(\", \")));\n        setCategories(po.getCategories().stream().map(CategoryRelationVOForList::fromEntity).collect(Collectors.toSet()));\n    }\n\n    public static DealVOForList fromEntity(Deal po) {\n        DealVOForList vo = new DealVOForList();\n        vo.setValue(po);\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/debt_account/DebtAccount.java",
    "content": "package com.jiukuaitech.bookkeeping.user.debt_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.validation.AprValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.CreditLimitValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.Column;\nimport javax.persistence.DiscriminatorValue;\nimport javax.persistence.Entity;\nimport java.math.BigDecimal;\n\n/**\n * 负债账户，例如房贷，车贷，应付账款，借呗等。一般不能直接用于支出和收入。\n */\n@Entity\n@DiscriminatorValue(value = \"3\")\n@Getter\n@Setter\npublic class DebtAccount extends Account {\n\n    @Column(name = \"credit_limit\")\n    @CreditLimitValidator\n    private BigDecimal limit; // 信用额度\n\n    @AprValidator\n    private BigDecimal apr; // 年化利率(%)\n\n    public DebtAccount() { }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/debt_account/DebtAccountAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.debt_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountAddRequest;\nimport com.jiukuaitech.bookkeeping.user.validation.AprValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.CreditLimitValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class DebtAccountAddRequest extends AccountAddRequest {\n\n    @AprValidator\n    private BigDecimal apr;\n\n    @CreditLimitValidator\n    private BigDecimal limit; // 信用额度\n\n    public void copyPrimitive(DebtAccount po) {\n        super.copyPrimitive(po);\n        po.setApr(apr);\n        po.setLimit(limit);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/debt_account/DebtAccountController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.debt_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.account.AccountService;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n\n@RestController\n@RequestMapping(\"/debt-accounts\")\npublic class DebtAccountController extends BaseController {\n\n    @Resource\n    private DebtAccountService debtAccountService;\n\n    @Resource\n    private AccountService accountService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid AccountQueryRequest request,\n            @PageableDefault(sort = \"balance\", direction = Sort.Direction.ASC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(debtAccountService.query(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody DebtAccountAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(accountService.add(3, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/sum\")\n    public BaseResponse handleSum(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(debtAccountService.sum(userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/debt_account/DebtAccountRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.debt_account;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\n\n@Repository\npublic interface DebtAccountRepository extends BaseRepository<DebtAccount, Integer> {\n\n/*\n    @Query(\"SELECT COALESCE(SUM(p.balance), 0), \" +\n            \"COALESCE(SUM(p.expense), 0), \" +\n            \"COALESCE(SUM(p.income), 0), \" +\n            \"COALESCE(SUM(p.transferFrom), 0), \" +\n            \"COALESCE(SUM(p.transferTo), 0), \" +\n            \"COALESCE(SUM(p.limit), 0) \" +\n            \"FROM DebtAccount p WHERE p.book = :book AND p.enable = true\")\n    List<BigDecimal[]> findSum(Book book);\n\n    @Query(\"SELECT COALESCE(SUM(p.balance), 0) FROM DebtAccount p WHERE p.book = :book AND p.enable = true AND p.include = true\")\n    BigDecimal findSumBalance(Book book);\n*/\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/debt_account/DebtAccountService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.debt_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.account.AccountSpec;\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.List;\n\n@Service\npublic class DebtAccountService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private DebtAccountRepository debtAccountRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    public Page<DebtAccountVOForList> query(AccountQueryRequest request, Pageable page, Integer userSignInId) {\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        Specification<DebtAccount> specification = AccountSpec.buildSpecification(request, group);\n        Page<DebtAccount> poPage = debtAccountRepository.findAll(specification, page);\n        Page<DebtAccountVOForList> voPage = poPage.map(DebtAccountVOForList::fromEntity);\n        voPage.map(vo->{\n            vo.setConvertedBalance(currencyService.convert(vo.getBalance(), vo.getCurrencyCode(), group.getDefaultCurrencyCode()));\n            return vo;\n        });\n        return voPage;\n    }\n\n    public DebtAccountSumVO sum(Integer userSignInId) {\n        DebtAccountSumVO vo = new DebtAccountSumVO();\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<DebtAccount> accounts = debtAccountRepository.findAll(AccountSpec.inGroupAndEnable(group));\n        BigDecimal balance = BigDecimal.ZERO;\n        BigDecimal limit = BigDecimal.ZERO;\n        BigDecimal remainLimit = BigDecimal.ZERO;\n        for (int i = 0; i < accounts.size(); i++) {\n            DebtAccount account = accounts.get(i);\n            balance = balance.add(currencyService.convert(account.getBalance(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n            if (account.getLimit() != null) {\n                limit = limit.add(currencyService.convert(account.getLimit(), account.getCurrencyCode(), group.getDefaultCurrencyCode()));\n            }\n            DebtAccountVOForList debtAccountVOForList = DebtAccountVOForList.fromEntity(account);\n            if (debtAccountVOForList.getLimit() != null) {\n                remainLimit = remainLimit.add(debtAccountVOForList.getRemainLimit());\n            }\n        }\n        vo.setBalance(balance);\n        vo.setLimit(limit);\n        vo.setRemainLimit(remainLimit);\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/debt_account/DebtAccountSumVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.debt_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountSumVO;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n\n@Getter\n@Setter\npublic class DebtAccountSumVO extends AccountSumVO {\n\n    private BigDecimal limit;\n\n    private BigDecimal remainLimit;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/debt_account/DebtAccountVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.debt_account;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountVOForExtend;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class DebtAccountVOForList extends AccountVOForExtend {\n\n    private BigDecimal apr;\n    private BigDecimal limit;\n\n    public static DebtAccountVOForList fromEntity(DebtAccount po) {\n        DebtAccountVOForList vo = new DebtAccountVOForList();\n        vo.setValue(po);\n        vo.setApr(po.getApr());\n        vo.setLimit(po.getLimit());\n        return vo;\n    }\n\n    public BigDecimal getRemainLimit() {\n        if (limit == null) return null;\n//        if (limit.signum() == 0) return BigDecimal.valueOf(0);\n        return limit.add(getBalance());\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/exception/GlobalExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.exception;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\n\nimport org.springframework.context.MessageSource;\nimport org.springframework.dao.DataIntegrityViolationException;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.converter.HttpMessageNotReadableException;\nimport org.springframework.validation.BindException;\nimport org.springframework.web.HttpMediaTypeNotSupportedException;\nimport org.springframework.web.HttpRequestMethodNotSupportedException;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.ResponseStatus;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\nimport org.springframework.web.servlet.NoHandlerFoundException;\n\nimport javax.annotation.Resource;\nimport javax.validation.ConstraintViolationException;\nimport java.util.Locale;\n\n@RestControllerAdvice\npublic class GlobalExceptionHandler {\n\n    @Resource\n    private MessageSource messageSource;\n\n    private static final Locale LANG = Locale.CHINA;\n\n\n    @ExceptionHandler(value = Exception.class)\n    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    public BaseResponse defaultExceptionHandler(Exception e) {\n        // TODO 日志记录\n        System.out.println(\"------------------------------------------\");\n        e.printStackTrace();\n        return new ErrorResponse(0, messageSource.getMessage(\"DefaultException\", null, LANG));\n    }\n\n    // 请求头的Content-Type不正确\n    @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class)\n    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)\n    public BaseResponse exceptionHandler1(Exception e) {\n        return new ErrorResponse(1, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    // 404\n    @ExceptionHandler(value = NoHandlerFoundException.class)\n    @ResponseStatus(HttpStatus.NOT_FOUND)\n    public BaseResponse exceptionHandler2(Exception e) {\n        return new ErrorResponse(404, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    // 更新的数据违反了数据库约束\n    @ExceptionHandler(value = {DataIntegrityViolationException.class, ConstraintViolationException.class})\n    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)\n    public BaseResponse exceptionHandler3(Exception e) {\n        e.printStackTrace();\n        return new ErrorResponse(3, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    // 前端传递的参数不合法。\n    @ExceptionHandler({\n            MethodArgumentNotValidException.class,\n            BindException.class,\n            InputNotValidException.class,\n            HttpMessageNotReadableException.class\n    })\n    @ResponseBody\n    public BaseResponse exceptionHandler6(Exception e) {\n        e.printStackTrace();\n        return new ErrorResponse(6, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    //\n    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)\n    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)\n    public BaseResponse exceptionHandler7(Exception e) {\n        return new ErrorResponse(7, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = TokenEmptyException.class)\n    @ResponseBody\n    public BaseResponse exceptionHandler8(Exception e) {\n        return new ErrorResponse(8, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = TokenNotValidException.class)\n    @ResponseBody\n    public BaseResponse exceptionHandler10(Exception e) {\n        return new ErrorResponse(10, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = PermissionException.class)\n    @ResponseStatus(HttpStatus.FORBIDDEN)\n    public BaseResponse exceptionHandler9(Exception e) {\n        return new ErrorResponse(9, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = ItemNotFoundException.class)\n    @ResponseStatus(HttpStatus.NOT_FOUND)\n    public BaseResponse handleException(ItemNotFoundException e) {\n        return new ErrorResponse(404, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = NameExistsException.class)\n    @ResponseStatus(HttpStatus.CONFLICT)\n    public BaseResponse handleException(NameExistsException e) {\n        return new ErrorResponse(408, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/exception/InputNotValidException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.exception;\n\npublic class InputNotValidException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/exception/ItemNotFoundException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.exception;\n\npublic class ItemNotFoundException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/exception/NameExistsException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.exception;\n\npublic class NameExistsException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/exception/PermissionException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.exception;\n\n\npublic class PermissionException extends RuntimeException {\n\n    private static final long serialVersionUID = -3240641871841704086L;\n\n    public PermissionException() {}\n\n    public PermissionException(String msg) {\n        super(msg);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/exception/TokenEmptyException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.exception;\n\npublic class TokenEmptyException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/exception/TokenNotValidException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.exception;\n\npublic class TokenNotValidException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/expense/Expense.java",
    "content": "package com.jiukuaitech.bookkeeping.user.expense;\n\nimport com.jiukuaitech.bookkeeping.user.deal.Deal;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\n\n@Entity\n@DiscriminatorValue(value = \"1\")\n@Getter\n@Setter\n// n+1\n//@NamedEntityGraph(name = \"Expense.Graph\", attributeNodes = {\n//        @NamedAttributeNode(\"tags\"),\n//        @NamedAttributeNode(\"categories\"),\n//        @NamedAttributeNode(\"payee\"),\n//        @NamedAttributeNode(\"account\"),\n//})\npublic class Expense extends Deal {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/expense/ExpenseController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.expense;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.deal.DealAddRequest;\nimport com.jiukuaitech.bookkeeping.user.deal.DealUpdateRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.deal.DealService;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/expenses\")\npublic class ExpenseController extends BaseController {\n\n    @Resource\n    private ExpenseService expenseService;\n\n    @Resource\n    private DealService dealService;\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody DealAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        dealService.add(1, request, userSignInId, false);\n        return new BaseResponse(true);\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid BalanceFlowQueryRequest request,\n            @PageableDefault(sort = {\"createTime\", \"id\"}, direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(expenseService.queryWithDefaultBook(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody DealUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(dealService.update(1, id, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"{id}/refund\")\n    public BaseResponse handleRefund(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody DealAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(dealService.refund(1, id, request, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/expense/ExpenseRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.expense;\n\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.stereotype.Repository;\nimport java.math.BigDecimal;\n\n\n@Repository\npublic interface ExpenseRepository extends HasBookRepository<Expense> {\n\n    @Query(\"SELECT COALESCE(SUM(p.convertedAmount), 0) FROM Expense p WHERE p.book = :book AND p.status <> 2 AND p.createTime BETWEEN :start AND :end\")\n    BigDecimal findSumAmount(Book book, Long start, Long end);\n\n    @Query(\"SELECT COUNT(p) FROM Expense p WHERE p.book = :book AND p.status <> 2 AND p.createTime BETWEEN :start AND :end\")\n    Integer findCount(Book book, Long start, Long end);\n\n    // n+1\n//    @EntityGraph(value = \"Expense.Graph\", type = EntityGraph.EntityGraphType.FETCH)\n//    List<Expense> findAll(Specification<Expense> specification);\n//\n//    @EntityGraph(value = \"Expense.Graph\", type = EntityGraph.EntityGraphType.FETCH)\n//    Page<Expense> findAll(Specification<Expense> specification, Pageable page);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/expense/ExpenseService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.expense;\n\nimport com.jiukuaitech.bookkeeping.user.deal.DealQueryResultVO;\nimport com.jiukuaitech.bookkeeping.user.deal.DealSpec;\nimport com.jiukuaitech.bookkeeping.user.deal.DealVOForList;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\n\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\n\n@Service\npublic class ExpenseService {\n\n    @Resource\n    private ExpenseRepository expenseRepository;\n\n    @Resource\n    private UserService userService;\n\n    public DealQueryResultVO queryWithDefaultBook(BalanceFlowQueryRequest request, Pageable page, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        request.setBookId(user.getDefaultBook().getId());\n        return query(request, page, userSignInId);\n    }\n\n    public DealQueryResultVO query(BalanceFlowQueryRequest request, Pageable page, Integer userSignInId) {\n        DealQueryResultVO result = new DealQueryResultVO();\n        // 缓存了，可以避免n+1\n//        payeeRepository.findByBookAndEnableAndExpenseable(user.getDefaultBook(), true, true);\n//        accountRepository.findByBookAndEnableAndExpenseable(user.getDefaultBook(), true, true);\n//        expenseCategoryRepository.findByBook(user.getDefaultBook());\n//        tagRepository.findByBookAndEnableAndExpenseable(user.getDefaultBook(), true, true);\n//        Specification<Expense> specification = (root, query, criteriaBuilder) -> {\n//            List<Predicate> predicates = DealUtils.buildExpensePredicates(request, user, root, criteriaBuilder);\n            // join查询，避免n+1\n//            root.fetch(\"payee\");\n//            root.fetch(\"account\");\n//            root.fetch(\"categories\");\n//            root.fetch(\"tags\");\n//            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));\n//        };\n        Group defaultGroup = userService.getUser(userSignInId).getDefaultGroup();\n        Specification<Expense> specification = DealSpec.buildSpecification(request, defaultGroup);\n        Page<Expense> poPage = expenseRepository.findAll(specification, page);\n        Page<DealVOForList> voPage = poPage.map(expense -> {\n            DealVOForList vo = DealVOForList.fromEntity(expense);\n            vo.setCurrencyCode(expense.getAccount().getCurrencyCode());\n            if (expense.getBook().getDefaultCurrencyCode().equals(expense.getAccount().getCurrencyCode())) {\n                vo.setNeedConvert(false);\n            } else {\n                vo.setNeedConvert(true);\n                vo.setToCurrencyCode(expense.getBook().getDefaultCurrencyCode());\n            }\n            return vo;\n        });\n        result.setResult(voPage);\n/*\n        CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();\n        CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();\n        Root<Expense> root = query.from(Expense.class);\n        query.select(criteriaBuilder.tuple(criteriaBuilder.sum(root.get(Expense_.amount))));\n        query.where(predicates.toArray(new Predicate[0]));\n        TypedQuery<Tuple> resultQuery = em.createQuery(query);\n        Tuple tuple = resultQuery.getSingleResult();\n        result.setTotal(tuple.get(0) == null ? BigDecimal.valueOf(0) : (BigDecimal) tuple.get(0));\n        */\n\n        // 解决join查询重复问题，应该考虑子查询\n        if (CollectionUtils.isEmpty(request.getTags())) {\n            result.setTotal(expenseRepository.calcAggregate(specification, Expense_.convertedAmount, Expense.class));\n        } else {\n            result.setTotal(expenseRepository.findAll(specification).stream().map(Expense::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n        }\n\n        return result;\n    }\n\n}"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/expense_category/ExpenseCategory.java",
    "content": "package com.jiukuaitech.bookkeeping.user.expense_category;\n\nimport javax.persistence.*;\n\nimport com.jiukuaitech.bookkeeping.user.category.Category;\nimport lombok.NoArgsConstructor;\n\n/**\n * 支出类别\n */\n@Entity\n@DiscriminatorValue(value = \"1\")\n@NoArgsConstructor\npublic class ExpenseCategory extends Category {\n\n    public ExpenseCategory(Integer id) {\n        super(id);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/expense_category/ExpenseCategoryController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.expense_category;\n\nimport com.jiukuaitech.bookkeeping.user.category.CategoryAddRequest;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryService;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryUpdateRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/expense-categories\")\npublic class ExpenseCategoryController extends BaseController {\n\n    @Resource\n    private CategoryService categoryService;\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody CategoryAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(categoryService.add(1, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/enable\")\n    public BaseResponse handleGetAllEnable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(categoryService.getAllEnable(1, userSignInId));\n    }\n\n    // 接口没用上\n    @RequestMapping(method = RequestMethod.GET, value = \"/simple\")\n    public BaseResponse handleQuerySimple(\n            @PageableDefault(sort = \"id\", direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(categoryService.query(1, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(@Valid CategoryQueryRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(categoryService.getAllTree(request, 1, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody CategoryUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(categoryService.update(1, id, request, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/FlowImage.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlow;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name=\"t_flow_image\")\n@Getter\n@Setter\npublic class FlowImage extends BaseEntity {\n\n    @Column(length = 256)\n    private String host;\n\n    @Column(length = 128)\n    private String uri;\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"user_id\")\n    @NotNull\n    private User user;\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"flow_id\")\n    private BalanceFlow flow;\n\n    @Column(nullable = false)\n    @NotNull\n    @TimeValidator\n    private Long createTime;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/FlowImageController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\n\n@RestController\n@RequestMapping(\"/flow-images\")\npublic class FlowImageController {\n\n    @Resource\n    private FlowImageService flowImageService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/upload-token\")\n    public BaseResponse handleUploadToken(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(flowImageService.uploadToken(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"/upload-callback\")\n    public BaseResponse handleUploadCallBack(@RequestBody UploadCallbackRequest request) {\n        return new DataResponse<>(flowImageService.uploadCallBack(request));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(flowImageService.remove(id, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/FlowImageExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class FlowImageExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = ImageExistsException.class)\n    @ResponseBody\n    public BaseResponse handleException(ImageExistsException e) {\n        return new ErrorResponse(702, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = UploadKeyEmptyException.class)\n    @ResponseBody\n    public BaseResponse handleException(UploadKeyEmptyException e) {\n        return new ErrorResponse(703, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/FlowImageRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlow;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\nimport java.util.List;\nimport java.util.Optional;\n\n\n@Repository\npublic interface FlowImageRepository extends BaseRepository<FlowImage, Integer> {\n\n    Optional<FlowImage> findByUserAndUri(User user, String uri);\n\n    List<FlowImage> findByFlow(BalanceFlow flow);\n\n    Optional<FlowImage> findByUserAndId(User user, Integer id);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/FlowImageService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\n\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.qiniu.util.Auth;\nimport com.qiniu.util.StringMap;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.StringUtils;\n\nimport javax.annotation.Resource;\nimport java.time.Instant;\nimport java.util.Optional;\n\n@Service\npublic class FlowImageService {\n\n    @Resource\n    private FlowImageRepository flowImageRepository;\n\n    @Resource\n    private UserService userService;\n\n    @Value(\"${upload.ACCESS_KEY}\")\n    private String uploadAK;\n\n    @Value(\"${upload.SECRET_KEY}\")\n    private String uploadSK;\n\n    @Value(\"${upload.FLOW_IMAGE_BUCKET}\")\n    private String uploadBucket;\n\n    @Value(\"${upload.FLOW_IMAGE_HOST}\")\n    private String imageHost;\n\n    @Value(\"${upload.FLOW_IMAGE_CALL_BACK_URL}\")\n    private String callBackUrl;\n\n    public String uploadToken(Integer userSignInId) {\n        if (!StringUtils.hasText(uploadAK) ||\n            !StringUtils.hasText(uploadSK) ||\n            !StringUtils.hasText(uploadBucket) ||\n            !StringUtils.hasText(imageHost) ||\n            !StringUtils.hasText(callBackUrl)\n        ) {\n            throw new UploadKeyEmptyException();\n        }\n        Auth auth = Auth.create(uploadAK, uploadSK);\n        StringMap putPolicy = new StringMap();\n        String saveKey = userSignInId.toString() + \"_\" + \"$(year)$(mon)$(day)$(hour)$(min)$(sec)\" + \"$(ext)\";\n        putPolicy.put(\"saveKey\", saveKey);\n        putPolicy.put(\"fsizeMin\", 1024); //1KB\n        putPolicy.put(\"fsizeLimit\", 15728640); //15M 用15*1024*1024报错\n        putPolicy.put(\"mimeLimit\", \"image/jpeg;image/jpg;;image/png;application/pdf\");\n        putPolicy.put(\"fileType\", 0); //0 为标准存储（默认），1 为低频存储，2 为归档存储。\n        // 自定义上传回复的凭证\n//        putPolicy.put(\"returnBody\", \"{\\\"key\\\":\\\"$(key)}\\\"\");\n        long expireSeconds = 3600;\n        // 带回调业务服务器的凭证\n        putPolicy.put(\"callbackUrl\", callBackUrl);\n        putPolicy.put(\"callbackBody\", \"{\\\"key\\\":\\\"$(key)\\\",\\\"userId\\\":$(x:userId)}\");\n        putPolicy.put(\"callbackBodyType\", \"application/json\");\n        return auth.uploadToken(uploadBucket, null, expireSeconds, putPolicy);\n    }\n\n    public FlowImageVOForList uploadCallBack(UploadCallbackRequest request) {\n        User user = new User(request.getUserId());\n        FlowImage image;\n        Optional<FlowImage> optionalImage = flowImageRepository.findByUserAndUri(user, request.getKey());\n        if(optionalImage.isPresent()) {\n            image = optionalImage.get();\n            if (image.getFlow() != null) throw new ImageExistsException();\n        } else {\n            image = new FlowImage();\n            image.setHost(imageHost);\n            image.setUri(\"/\" + request.getKey());\n            image.setUser(user);\n            image.setCreateTime(Instant.now().toEpochMilli());\n            flowImageRepository.save(image);\n        }\n        return FlowImageVOForList.fromEntity(image);\n    }\n\n    public boolean remove(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        FlowImage image = flowImageRepository.findByUserAndId(user, id).orElseThrow(ItemNotFoundException::new);\n        image.setFlow(null);\n        flowImageRepository.save(image);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/FlowImageVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class FlowImageVOForList {\n\n    private Integer id;\n    private String url;\n    private Integer userId;\n    private Integer flowId;\n\n    public static FlowImageVOForList fromEntity(FlowImage po) {\n        if (po == null) return null;\n        FlowImageVOForList vo = new FlowImageVOForList();\n        vo.setId(po.getId());\n        vo.setUrl(po.getHost() + po.getUri());\n        vo.setUserId(po.getUser().getId());\n        if (po.getFlow() != null) vo.setFlowId(po.getFlow().getId());\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/ImageExistsException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\n\npublic class ImageExistsException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/UploadCallbackRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class UploadCallbackRequest {\n\n    private Integer userId;\n    private String key;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/flow_images/UploadKeyEmptyException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.flow_images;\n\npublic class UploadKeyEmptyException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/Group.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserGroupRelation;\nimport com.jiukuaitech.bookkeeping.user.base.NameNotesEnableEntity;\nimport com.jiukuaitech.bookkeeping.user.validation.AvatarValidator;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@Entity\n@Table(name = \"t_group\")\n@Getter\n@Setter\n/**\n * 多个人一个组一起记账。\n * 组名name，可以重复。\n */\npublic class Group extends NameNotesEnableEntity {\n\n    @ManyToOne(optional = true, fetch = FetchType.LAZY)\n    private User creator;\n\n    @Column(length = 128)\n    @AvatarValidator\n    private String avatar;\n\n    @OneToMany(mappedBy = \"group\", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)\n    private Set<UserGroupRelation> relations = new HashSet<>();\n\n    @ManyToOne(optional = true, fetch = FetchType.LAZY)\n    private Book defaultBook; //组默认操作的账本\n\n    @Column(nullable = false, length = 8)\n    @NotNull\n    private String defaultCurrencyCode;//默认的币种\n\n    public Group() { }\n\n    public Group(Integer id) {\n        super.setId(id);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotBlank;\n\n@Getter\n@Setter\npublic class GroupAddRequest {\n\n    @NotBlank(message=\"name must not be blank\")\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    @NotBlank\n    private String defaultCurrencyCode;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/groups\")\npublic class GroupController {\n\n    @Resource\n    private GroupService groupService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @PageableDefault(sort = \"id\", direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(groupService.query(page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/enable\")\n    public BaseResponse handleEnable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(groupService.getEnable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(@Valid @RequestBody GroupAddRequest groupAddRequest, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(groupService.add(groupAddRequest, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody GroupUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(groupService.update(id, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(groupService.remove(id, userSignInId));\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class GroupExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = GroupHasBookException.class)\n    @ResponseBody\n    public BaseResponse handleException(GroupHasBookException e) {\n        return new ErrorResponse(601, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = GroupMaxCountException.class)\n    @ResponseBody\n    public BaseResponse handleException(GroupMaxCountException e) {\n        return new ErrorResponse(602, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupHasBookException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\npublic class GroupHasBookException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupMaxCountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\npublic class GroupMaxCountException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport org.springframework.stereotype.Repository;\nimport java.util.Optional;\n\n\n@Repository\npublic interface GroupRepository extends BaseRepository<Group, Integer> {\n\n    long countByCreator(User creator);\n\n    Optional<Group> findOneByCreatorAndId(User user, Integer id);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.user.*;\nimport com.jiukuaitech.bookkeeping.user.book.BookRepository;\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.exception.PermissionException;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.StringUtils;\nimport javax.annotation.Resource;\nimport javax.persistence.criteria.Join;\nimport javax.persistence.criteria.Predicate;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Service\npublic class GroupService {\n\n    @Resource\n    private UserGroupRelationRepository userGroupRelationRepository;\n\n    @Resource\n    private GroupRepository groupRepository;\n\n    @Resource\n    private BookRepository bookRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    @Resource\n    private UserService userService;\n\n    @Value(\"${group.max.count}\")\n    private Integer groupMaxCount;\n\n    public Page<GroupVOForList> query(Pageable page, Integer userSignInId) {\n        Specification<Group> specification = (root, query, criteriaBuilder) -> {\n            List<Predicate> predicates = new ArrayList<>();\n            query.distinct(true);\n            Join<Group, UserGroupRelation> join = root.join(Group_.relations);\n            predicates.add(criteriaBuilder.equal(join.get(UserGroupRelation_.user), userSignInId));\n            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));\n        };\n        Page<Group> poPage = groupRepository.findAll(specification, page);\n        return poPage.map(GroupVOForList::fromEntity);\n    }\n\n    public List<GroupVOForList> getEnable(Integer userSignInId) {\n        Specification<Group> specification = (root, query, criteriaBuilder) -> {\n            List<Predicate> predicates = new ArrayList<>();\n            Join<Group, UserGroupRelation> join = root.join(Group_.relations);\n            predicates.add(criteriaBuilder.equal(join.get(UserGroupRelation_.user), userSignInId));\n            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));\n        };\n        List<Group> entityList = groupRepository.findAll(specification);\n        return entityList.stream().map(GroupVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    @Transactional\n    public boolean add(GroupAddRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        if (groupRepository.countByCreator(user) >= groupMaxCount) {\n            throw new GroupMaxCountException();\n        }\n        currencyService.checkCode(request.getDefaultCurrencyCode());\n        Group po = new Group();\n        po.setName(request.getName());\n        po.setNotes(request.getNotes());\n        po.setDefaultCurrencyCode(request.getDefaultCurrencyCode());\n        po.setCreator(user);\n        groupRepository.save(po);\n        Book book = new Book();\n        book.setName(\"默认账本\");\n        book.setDefaultCurrencyCode(po.getDefaultCurrencyCode());\n        book.setGroup(po);\n        bookRepository.save(book);\n        po.setDefaultBook(book);\n        groupRepository.save(po);\n        UserGroupRelation userGroupRelationToAdd = new UserGroupRelation(user, po, 1);\n        userGroupRelationRepository.save(userGroupRelationToAdd);\n        return true;\n    }\n\n    public boolean update(Integer id, GroupUpdateRequest request, Integer userSignInId) {\n        Group po = groupRepository.findById(id).orElseThrow(ItemNotFoundException::new);\n        UserGroupRelation relation = userGroupRelationRepository.findOneByUserAndGroup(new User(userSignInId), po);\n        if (relation == null) throw new PermissionException(\"No Permission\");\n        if (StringUtils.hasText(request.getName())) po.setName(request.getName());\n        if (request.getNotes() != null) po.setNotes(request.getNotes());\n        groupRepository.save(po);\n        return true;\n    }\n\n    @Transactional\n    public boolean remove(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Group po = groupRepository.findOneByCreatorAndId(user, id).orElseThrow(ItemNotFoundException::new);\n        po.setDefaultBook(null);\n        bookRepository.deleteByGroup_id(id);\n        // 检查关联性\n//        if (bookRepository.countByGroup_id(id) > 0) {\n//            throw new GroupHasBookException();\n//        }\n//        UserGroupRelation relation = userGroupRelationRepository.findOneByUserAndGroup(new User(userSignInId), po);\n//        if (relation == null) throw new PermissionException(\"No Permission\");\n        userGroupRelationRepository.deleteByGroup(po);\n        groupRepository.delete(po);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class GroupUpdateRequest {\n\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/group/GroupVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.group;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class GroupVOForList {\n\n    private Integer id;\n    private String name;\n    private String notes;\n    private String defaultCurrencyCode;\n    private Integer role;\n\n    public static GroupVOForList fromEntity(Group po) {\n        GroupVOForList vo = new GroupVOForList();\n        vo.setId(po.getId());\n        vo.setName(po.getName());\n        vo.setNotes(po.getNotes());\n        vo.setDefaultCurrencyCode(po.getDefaultCurrencyCode());\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/income/Income.java",
    "content": "package com.jiukuaitech.bookkeeping.user.income;\n\nimport com.jiukuaitech.bookkeeping.user.deal.Deal;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\n\n@Entity\n@DiscriminatorValue(value = \"2\")\n@Getter\n@Setter\npublic class Income extends Deal {\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/income/IncomeController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.income;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.deal.DealAddRequest;\nimport com.jiukuaitech.bookkeeping.user.deal.DealService;\nimport com.jiukuaitech.bookkeeping.user.deal.DealUpdateRequest;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n\n@RestController\n@RequestMapping(\"/incomes\")\npublic class IncomeController extends BaseController {\n\n    @Resource\n    private IncomeService incomeService;\n\n    @Resource\n    private DealService dealService;\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody DealAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        dealService.add(2, request, userSignInId, false);\n        return new BaseResponse(true);\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid BalanceFlowQueryRequest request,\n            @PageableDefault(sort = {\"createTime\", \"id\"}, direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(incomeService.queryWithDefaultBook(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody DealUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(dealService.update(2, id, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"{id}/refund\")\n    public BaseResponse handleRefund(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody DealAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(dealService.refund(2, id, request, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/income/IncomeRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.income;\n\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport org.springframework.data.jpa.repository.Query;\nimport org.springframework.stereotype.Repository;\n\nimport java.math.BigDecimal;\n\n\n@Repository\npublic interface IncomeRepository extends HasBookRepository<Income> {\n\n    @Query(\"SELECT COALESCE(SUM(p.convertedAmount), 0) FROM Income p WHERE p.book = :book AND p.status <> 2 AND p.createTime BETWEEN :start AND :end\")\n    BigDecimal findSumAmount(Book book, Long start, Long end);\n\n    @Query(\"SELECT COUNT(p) FROM Income p WHERE p.book = :book AND p.status <> 2 AND p.createTime BETWEEN :start AND :end\")\n    Integer findCount(Book book, Long start, Long end);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/income/IncomeService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.income;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.deal.DealQueryResultVO;\nimport com.jiukuaitech.bookkeeping.user.deal.DealSpec;\nimport com.jiukuaitech.bookkeeping.user.deal.DealVOForList;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\n\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\n\n@Service\npublic class IncomeService {\n\n    @Resource\n    private IncomeRepository incomeRepository;\n\n    @Resource\n    private UserService userService;\n\n    public DealQueryResultVO queryWithDefaultBook(BalanceFlowQueryRequest request, Pageable page, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        request.setBookId(user.getDefaultBook().getId());\n        return query(request, page, userSignInId);\n    }\n\n    public DealQueryResultVO query(BalanceFlowQueryRequest request, Pageable page, Integer userSignInId) {\n        DealQueryResultVO result = new DealQueryResultVO();\n        Group defaultGroup = userService.getUser(userSignInId).getDefaultGroup();\n        Specification<Income> specification = DealSpec.buildSpecification(request, defaultGroup);\n        Page<Income> poPage = incomeRepository.findAll(specification, page);\n        Page<DealVOForList> voPage = poPage.map(income -> {\n            DealVOForList vo = DealVOForList.fromEntity(income);\n            vo.setCurrencyCode(income.getAccount().getCurrencyCode());\n            if (income.getBook().getDefaultCurrencyCode().equals(income.getAccount().getCurrencyCode())) {\n                vo.setNeedConvert(false);\n            } else {\n                vo.setNeedConvert(true);\n                vo.setToCurrencyCode(income.getBook().getDefaultCurrencyCode());\n            }\n            return vo;\n        });\n        result.setResult(voPage);\n        if (CollectionUtils.isEmpty(request.getTags())) {\n            result.setTotal(incomeRepository.calcAggregate(specification, Income_.convertedAmount, Income.class));\n        } else {\n            result.setTotal(incomeRepository.findAll(specification).stream().map(Income::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n        }\n        return result;\n    }\n\n}"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/income_category/IncomeCategory.java",
    "content": "package com.jiukuaitech.bookkeeping.user.income_category;\n\nimport javax.persistence.*;\n\nimport com.jiukuaitech.bookkeeping.user.category.Category;\nimport lombok.NoArgsConstructor;\n\n/**\n * 收入类别\n */\n@Entity\n@DiscriminatorValue(value = \"2\")\n@NoArgsConstructor\npublic class IncomeCategory extends Category {\n\n    public IncomeCategory(Integer id) {\n        super(id);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/income_category/IncomeCategoryController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.income_category;\n\nimport com.jiukuaitech.bookkeeping.user.category.CategoryAddRequest;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryService;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryUpdateRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/income-categories\")\npublic class IncomeCategoryController extends BaseController {\n\n    @Resource\n    private CategoryService categoryService;\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody CategoryAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(categoryService.add(2, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/enable\")\n    public BaseResponse handleGetAllEnable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(categoryService.getAllEnable(2, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(@Valid CategoryQueryRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(categoryService.getAllTree(request, 2, userSignInId));\n    }\n\n    // 接口没用上\n    @RequestMapping(method = RequestMethod.GET, value = \"/simple\")\n    public BaseResponse handleQuerySimple(\n            @PageableDefault(sort = \"id\", direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(categoryService.query(2, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody CategoryUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(categoryService.update(2, id, request, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/interceptor/AuthInterceptor.java",
    "content": "package com.jiukuaitech.bookkeeping.user.interceptor;\n\nimport com.jiukuaitech.bookkeeping.user.exception.TokenEmptyException;\nimport com.jiukuaitech.bookkeeping.user.exception.TokenNotValidException;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.servlet.HandlerInterceptor;\nimport org.springframework.web.util.WebUtils;\n\nimport javax.annotation.Resource;\nimport javax.servlet.ServletContext;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@Component\npublic class AuthInterceptor implements HandlerInterceptor {\n\n    @Resource\n    private ServletContext servletContext;\n    \n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {\n        String userToken = request.getHeader(\"User-Token\");\n        // try session\n        if (!StringUtils.hasText(userToken)) {\n            userToken = (String) request.getSession().getAttribute(\"User-Token\");\n        }\n        // try cookie\n        if (!StringUtils.hasText(userToken)) {\n            if (WebUtils.getCookie(request, \"User-Token\") != null) {\n                userToken = WebUtils.getCookie(request, \"User-Token\").getValue();\n            }\n        }\n        if (!StringUtils.hasText(userToken)) {\n            throw new TokenEmptyException();\n        }\n        Integer userSignInId = (Integer) servletContext.getAttribute(userToken);\n        if (userSignInId == null) throw new TokenNotValidException();\n        request.setAttribute(\"userSignInId\", userSignInId);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/interceptor/MvcInterceptorConfig.java",
    "content": "package com.jiukuaitech.bookkeeping.user.interceptor;\n\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.web.PageableHandlerMethodArgumentResolver;\nimport org.springframework.web.method.support.HandlerMethodArgumentResolver;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\nimport javax.annotation.Resource;\nimport java.util.List;\n\n@Configuration\npublic class MvcInterceptorConfig implements WebMvcConfigurer {\n\n    @Resource\n    private AuthInterceptor authInterceptor;\n\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n        registry.addInterceptor(authInterceptor).addPathPatterns(\"/**\")\n                .excludePathPatterns(\"/signin\", \"/register\", \"/flow-images/upload-callback\", \"/test*\", \"/currency/initData\", \"/currency/initData2\");\n        WebMvcConfigurer.super.addInterceptors(registry);\n    }\n\n    @Override\n    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {\n        PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();\n        resolver.setOneIndexedParameters(true);\n        resolvers.add(resolver);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/interceptor/StringTrimModule.java",
    "content": "package com.jiukuaitech.bookkeeping.user.interceptor;\n\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;\nimport com.fasterxml.jackson.databind.module.SimpleModule;\nimport org.springframework.stereotype.Component;\n\nimport java.io.IOException;\n\n@Component\n// 前端参数去前后空格\n// https://stackoverflow.com/questions/6852213/can-jackson-be-configured-to-trim-leading-trailing-whitespace-from-all-string-pr/24077444\n// https://stackoverflow.com/questions/2691667/can-spring-mvc-trim-all-strings-obtained-from-forms\npublic class StringTrimModule extends SimpleModule {\n\n    public StringTrimModule() {\n        addDeserializer(String.class, new StdScalarDeserializer<String>(String.class) {\n            @Override\n            public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {\n                return jsonParser.getValueAsString().trim();\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/Item.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name = \"t_item\")\n@Getter\n@Setter\npublic class Item extends BaseEntity {\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @NotNull\n    @JoinColumn(name = \"user_id\")\n    private User user;\n\n    @Column(length = 16, nullable = false)\n    @NotNull\n    @NameValidator\n    private String title;\n\n    @Column(length = 1024)\n    @NotesValidator\n    private String notes; //备注\n\n    @Column(nullable = false)\n    @NotNull\n    @TimeValidator\n    private Long startDate; //起始日期\n\n    @Column\n    @NotNull\n    @TimeValidator\n    private Long endDate; //结束日期\n\n    @Column\n    @TimeValidator\n    private Long nextDate; //下次执行日期\n\n    @Column\n    @NotNull\n    private Integer repeatType; //0单次 1每天，2每月 3每年\n\n    @Column(name = \"c_interval\")\n    private Integer interval; //间隔\n\n    //总执行次数\n    @NotNull\n    private Integer totalCount;\n\n    //已执行次数\n    @NotNull\n    private Integer runCount;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.NotNull;\n\n@Getter\n@Setter\npublic class ItemAddRequest {\n\n    @NotNull\n    private Integer type;\n\n    @NotBlank(message=\"name must not be blank\")\n    @NameValidator\n    private String title;\n\n    private String notes;\n\n    @TimeValidator\n    private Long startDate;\n\n    @TimeValidator\n    private Long endDate;\n\n    private Integer repeatType;\n\n    private Integer interval;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/items\")\npublic class ItemController {\n\n    @Resource\n    private ItemService itemService;\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody ItemAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(itemService.add(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/run\")\n    public BaseResponse handleRun(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(itemService.run(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/recall\")\n    public BaseResponse handleRecall(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(itemService.recall(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid ItemQueryRequest request,\n            @PageableDefault(sort = \"id\", direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(itemService.query(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(itemService.remove(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody ItemUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(itemService.update(id, request, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemCountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\npublic class ItemCountException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class ItemExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = ItemCountException.class)\n    @ResponseBody\n    public BaseResponse handleException(ItemCountException e) {\n        return new ErrorResponse(601, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemQueryRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\npublic class ItemQueryRequest {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport org.springframework.stereotype.Repository;\nimport java.util.Optional;\n\n@Repository\npublic interface ItemRepository extends BaseRepository<Item, Integer> {\n\n    Optional<Item> findOneByUserAndTitle(User user, String title);\n\n    Optional<Item> findOneByUserAndId(User user, Integer id);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.exception.NameExistsException;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.StringUtils;\n\nimport javax.annotation.Resource;\nimport java.time.Instant;\nimport java.time.LocalDate;\nimport java.time.ZoneId;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Calendar;\n\n@Service\npublic class ItemService {\n\n    @Resource\n    private ItemRepository itemRepository;\n\n    @Resource\n    private UserService userService;\n\n    public boolean add(ItemAddRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        if (itemRepository.findOneByUserAndTitle(user, request.getTitle()).isPresent()) {\n            throw new NameExistsException();\n        }\n        Item po = new Item();\n        po.setUser(user);\n        po.setTitle(request.getTitle());\n        po.setNotes(request.getNotes());\n        po.setStartDate(request.getStartDate());\n        po.setNextDate(request.getStartDate());\n        po.setRunCount(0);\n        if (request.getType() == 1) {\n            po.setRepeatType(0);\n            po.setTotalCount(1);\n            po.setEndDate(request.getStartDate());\n        } else {\n            po.setRepeatType(request.getRepeatType());\n            po.setEndDate(request.getEndDate());\n            po.setInterval(request.getInterval());\n            //计算总执行次数\n            LocalDate startDate = Instant.ofEpochMilli(po.getStartDate()).atZone(ZoneId.systemDefault()).toLocalDate();\n            LocalDate endDate = Instant.ofEpochMilli(po.getEndDate()).atZone(ZoneId.systemDefault()).toLocalDate();\n            int totalCount = 0;\n            switch (request.getRepeatType()) {\n                case 1:\n                    Long days = ChronoUnit.DAYS.between(\n                            LocalDate.of(startDate.getYear(), startDate.getMonth(), startDate.getDayOfMonth()),\n                            LocalDate.of(endDate.getYear(), endDate.getMonth(), endDate.getDayOfMonth())\n                    );\n                    totalCount = (int)(days / po.getInterval());\n                    break;\n                case 2:\n                    Long months = ChronoUnit.MONTHS.between(\n                            LocalDate.of(startDate.getYear(), startDate.getMonth(), startDate.getDayOfMonth()),\n                            LocalDate.of(endDate.getYear(), endDate.getMonth(), endDate.getDayOfMonth())\n                    );\n                    totalCount = (int)(months / po.getInterval());\n                    break;\n                case 3:\n                    Long years = ChronoUnit.YEARS.between(\n                            LocalDate.of(startDate.getYear(), startDate.getMonth(), startDate.getDayOfMonth()),\n                            LocalDate.of(endDate.getYear(), endDate.getMonth(), endDate.getDayOfMonth())\n                    );\n                    totalCount = (int)(years / po.getInterval());\n                    break;\n            }\n            po.setTotalCount(totalCount + 1);\n            po.setRunCount(0);\n        }\n        itemRepository.save(po);\n        return true;\n    }\n\n    public boolean run(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Item po = itemRepository.findOneByUserAndId(user, id).orElseThrow(ItemNotFoundException::new);\n        if (po.getRunCount() >= po.getTotalCount()) {\n            throw new ItemCountException();\n        }\n        Calendar nextDate = Calendar.getInstance();\n        nextDate.setTimeInMillis(po.getNextDate());\n        switch (po.getRepeatType()) {\n            case 1:\n                nextDate.add(Calendar.DATE, po.getInterval());\n                break;\n            case 2:\n                nextDate.add(Calendar.MONTH, po.getInterval());\n                break;\n            case 3:\n                nextDate.add(Calendar.YEAR, po.getInterval());\n                break;\n        }\n        po.setNextDate(nextDate.getTimeInMillis());\n        po.setRunCount(po.getRunCount() + 1);\n        itemRepository.save(po);\n        return true;\n    }\n\n    public boolean recall(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Item po = itemRepository.findOneByUserAndId(user, id).orElseThrow(ItemNotFoundException::new);\n        if (po.getRunCount() <= 0) {\n            throw new ItemCountException();\n        }\n        Calendar nextDate = Calendar.getInstance();\n        nextDate.setTimeInMillis(po.getNextDate());\n        switch (po.getRepeatType()) {\n            case 1:\n                nextDate.add(Calendar.DATE, po.getInterval()*(-1));\n                break;\n            case 2:\n                nextDate.add(Calendar.MONTH, po.getInterval()*(-1));\n                break;\n            case 3:\n                nextDate.add(Calendar.YEAR, po.getInterval()*(-1));\n                break;\n        }\n        po.setNextDate(nextDate.getTimeInMillis());\n        po.setRunCount(po.getRunCount() - 1);\n        itemRepository.save(po);\n        return true;\n    }\n\n    public boolean remove(Integer id, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Item po = itemRepository.findOneByUserAndId(user, id).orElseThrow(ItemNotFoundException::new);\n        itemRepository.delete(po);\n        return true;\n    }\n\n    public boolean update(Integer id, ItemUpdateRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Item po = itemRepository.findOneByUserAndId(user, id).orElseThrow(ItemNotFoundException::new);\n        if (StringUtils.hasText(request.getTitle())) {\n            if (!po.getTitle().equals(request.getTitle())) {\n                if (itemRepository.findOneByUserAndTitle(user, request.getTitle()).isPresent()) {\n                    throw new NameExistsException();\n                }\n            }\n        }\n        if (request.getNotes() != null) po.setNotes(request.getNotes());\n        if (request.getEndDate() != null) po.setEndDate(request.getEndDate());\n        //计算总执行次数\n        LocalDate startDate = Instant.ofEpochMilli(po.getStartDate()).atZone(ZoneId.systemDefault()).toLocalDate();\n        LocalDate endDate = Instant.ofEpochMilli(po.getEndDate()).atZone(ZoneId.systemDefault()).toLocalDate();\n        switch (po.getRepeatType()) {\n            case 1:\n                Long days = ChronoUnit.DAYS.between(\n                        LocalDate.of(startDate.getYear(), startDate.getMonth(), startDate.getDayOfMonth()),\n                        LocalDate.of(endDate.getYear(), endDate.getMonth(), endDate.getDayOfMonth())\n                );\n                po.setTotalCount((int)(days / po.getInterval()));\n                break;\n            case 2:\n                Long months = ChronoUnit.MONTHS.between(\n                        LocalDate.of(startDate.getYear(), startDate.getMonth(), startDate.getDayOfMonth()),\n                        LocalDate.of(endDate.getYear(), endDate.getMonth(), endDate.getDayOfMonth())\n                );\n                po.setTotalCount((int)(months / po.getInterval()));\n                break;\n            case 3:\n                Long years = ChronoUnit.YEARS.between(\n                        LocalDate.of(startDate.getYear(), startDate.getMonth(), startDate.getDayOfMonth()),\n                        LocalDate.of(endDate.getYear(), endDate.getMonth(), endDate.getDayOfMonth())\n                );\n                po.setTotalCount((int)(years / po.getInterval()));\n                break;\n        }\n        itemRepository.save(po);\n        return true;\n    }\n\n    public Page<ItemVOForList> query(ItemQueryRequest request, Pageable page, Integer userSignInId) {\n        Specification<Item> specification = ItemSpec.buildSpecification(request, userService.getUser(userSignInId));\n        Page<Item> poPage = itemRepository.findAll(specification, page);\n        return poPage.map(ItemVOForList::fromEntity);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport org.springframework.data.jpa.domain.Specification;\n\npublic final class ItemSpec {\n\n    public static Specification<Item> isUser(User user) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(\"user\"), user);\n    }\n\n\n    public static Specification<Item> buildSpecification(ItemQueryRequest request, User user) {\n        return isUser(user);\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class ItemUpdateRequest {\n\n    @NameValidator\n    private String title;\n\n    private String notes;\n\n    @TimeValidator\n    private Long endDate;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/item/ItemVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.item;\n\nimport com.jiukuaitech.bookkeeping.user.utils.EnumUtils;\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.time.Instant;\nimport java.time.LocalDate;\nimport java.time.ZoneId;\nimport java.time.temporal.ChronoUnit;\n\n@Getter\n@Setter\npublic class ItemVOForList {\n\n    private  Integer id;\n    private String title;\n    private String notes;\n    private Long startDate;\n    private Long endDate;\n    private Integer repeatType;\n    private Integer interval;\n    private String repeatDescription;\n    private Long countDown;//倒计时天数\n    //下次执行日期\n    private Long nextDate;\n    //总执行次数\n    private Integer totalCount;\n    //已执行次数\n    private Integer runCount;\n    //剩余次数\n    private Integer remainCount;\n\n    public static ItemVOForList fromEntity(Item po) {\n        if (po == null) return null;\n        ItemVOForList vo = new ItemVOForList();\n        vo.setId(po.getId());\n        vo.setTitle(po.getTitle());\n        vo.setNotes(po.getNotes());\n        vo.setStartDate(po.getStartDate());\n        vo.setEndDate(po.getEndDate());\n        vo.setNextDate(po.getNextDate());\n        vo.setRepeatType(po.getRepeatType());\n        vo.setInterval(po.getInterval());\n        vo.setTotalCount(po.getTotalCount());\n        vo.setRunCount(po.getRunCount());\n        if (po.getRepeatType() == 0) {\n            vo.setRepeatDescription(\"单次执行\");\n        } else {\n            vo.setRepeatDescription(\"每\" + (po.getInterval() != 1 ? po.getInterval() : \"\") + EnumUtils.translateItemRepeatType(po.getRepeatType()) + \"执行一次\");\n        }\n        LocalDate now = LocalDate.now();\n        LocalDate nextDate = Instant.ofEpochMilli(vo.getNextDate()).atZone(ZoneId.systemDefault()).toLocalDate();\n        vo.setCountDown(ChronoUnit.DAYS.between(\n            LocalDate.of(now.getYear(), now.getMonth(), now.getDayOfMonth()),\n            LocalDate.of(nextDate.getYear(), nextDate.getMonth(), nextDate.getDayOfMonth())\n        ));\n        vo.setRemainCount(po.getTotalCount() - po.getRunCount());\n        return vo;\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/Payee.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableEntity;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name = \"t_payee\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"book_id\", \"name\"})})\n@Getter\n@Setter\n/**\n * 交易对象\n */\npublic class Payee extends BookNameNotesEnableEntity {\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean expenseable = true; //是否可支出\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean incomeable = true; //是否可收入\n\n    public Payee() { }\n\n    public Payee(Integer id) {\n        super.setId(id);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotEmpty;\n\n@Getter\n@Setter\npublic class PayeeAddRequest {\n\n    @NotEmpty\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    private Boolean expenseable = false;\n    private Boolean incomeable = false;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/payees\")\npublic class PayeeController extends BaseController {\n\n    @Resource\n    private PayeeService payeeService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @PageableDefault(sort = \"id\", direction = Sort.Direction.DESC) Pageable page,\n            @Valid PayeeQueryRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.query(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/{id}\")\n    public BaseResponse handleGet(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.get(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/enable\")\n    public BaseResponse handleEnable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.getEnable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/expenseable\")\n    public BaseResponse handleExpenseable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.getExpenseable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/incomeable\")\n    public BaseResponse handleIncomeable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.getIncomeable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody PayeeAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.add(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggle\")\n    public BaseResponse handleToggle(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.toggle(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody PayeeUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.update(id, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(payeeService.remove(id, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class PayeeExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = PayeeHasDealException.class)\n    @ResponseBody\n    public BaseResponse handleException(PayeeHasDealException e) {\n        return new ErrorResponse(409, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = PayeeMaxCountException.class)\n    @ResponseBody\n    public BaseResponse handleException(PayeeMaxCountException e) {\n        return new ErrorResponse(410, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeHasDealException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\n/*\n * 删除时有账单关联\n */\npublic class PayeeHasDealException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeMaxCountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\npublic class PayeeMaxCountException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeQueryRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class PayeeQueryRequest {\n\n    private String name;\n    private Boolean enable;\n    private Boolean expenseable;\n    private Boolean incomeable;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport org.springframework.stereotype.Repository;\nimport java.util.List;\nimport java.util.Optional;\n\n@Repository\npublic interface PayeeRepository extends HasBookRepository<Payee> {\n\n    // 添加时判断名称是否重复\n    Optional<Payee> findByBookAndName(Book book, String name);\n\n    List<Payee> findByBookAndEnable(Book book, Boolean enable);\n\n    List<Payee> findByBookAndEnableAndExpenseable(Book book, Boolean enable, Boolean expenseable);\n\n    List<Payee> findByBookAndEnableAndIncomeable(Book book, Boolean enable, Boolean incomeable);\n\n    long countByBook(Book book);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.deal.DealRepository;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.exception.NameExistsException;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Service\npublic class PayeeService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private PayeeRepository payeeRepository;\n\n    @Resource\n    private DealRepository dealRepository;\n\n    @Value(\"${payee.max.count}\")\n    private Integer maxCount;\n\n    public Page<PayeeVOForList> query(PayeeQueryRequest request, Pageable page, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Specification<Payee> specification = PayeeSpec.buildSpecification(request, book);\n        Page<Payee> poPage = payeeRepository.findAll(specification, page);\n        return poPage.map(PayeeVOForList::fromEntity);\n    }\n\n    public PayeeVOForList get(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Payee po = payeeRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        return PayeeVOForList.fromEntity(po);\n    }\n\n    public List<PayeeVOForList> getEnable(Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        List<Payee> entityList = payeeRepository.findByBookAndEnable(user.getDefaultBook(), true);\n        return entityList.stream().map(PayeeVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    public List<PayeeVOForList> getExpenseable(Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        List<Payee> entityList = payeeRepository.findByBookAndEnableAndExpenseable(user.getDefaultBook(), true, true);\n        return entityList.stream().map(PayeeVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    public List<PayeeVOForList> getIncomeable(Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        List<Payee> entityList = payeeRepository.findByBookAndEnableAndIncomeable(user.getDefaultBook(), true, true);\n        return entityList.stream().map(PayeeVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    public PayeeVOForList add(PayeeAddRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        // 不能重复\n        if (payeeRepository.findByBookAndName(book, request.getName()).isPresent()) {\n            throw new NameExistsException();\n        }\n        if (payeeRepository.countByBook(book) >= maxCount) {\n            throw new PayeeMaxCountException();\n        }\n        Payee po = new Payee();\n        po.setName(request.getName());\n        po.setNotes(request.getNotes());\n        po.setExpenseable(request.getExpenseable());\n        po.setIncomeable(request.getIncomeable());\n        po.setBook(book);\n        return PayeeVOForList.fromEntity(payeeRepository.save(po));\n    }\n\n    public PayeeVOForList remove(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Payee po = payeeRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        if (dealRepository.countByPayee_id(id) > 0) throw new PayeeHasDealException();\n        payeeRepository.delete(po);\n        return PayeeVOForList.fromEntity(po);\n    }\n\n    public PayeeVOForList update(Integer id, PayeeUpdateRequest request, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Payee po = payeeRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        if (!po.getName().equals(request.getName())) {\n            if (payeeRepository.findByBookAndName(po.getBook(), request.getName()).isPresent()) {\n                throw new NameExistsException();\n            }\n        }\n        if (request.getName() != null) po.setName(request.getName());\n        if (request.getNotes() != null) po.setNotes(request.getNotes());\n        if (request.getExpenseable() != null) po.setExpenseable(request.getExpenseable());\n        if (request.getIncomeable() != null) po.setIncomeable(request.getIncomeable());\n        return PayeeVOForList.fromEntity(payeeRepository.save(po));\n    }\n\n    public PayeeVOForList toggle(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Payee po = payeeRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        po.setEnable(!po.getEnable());\n        return PayeeVOForList.fromEntity(payeeRepository.save(po));\n    }\n\n}"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableSpec;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.util.StringUtils;\n\npublic final class PayeeSpec {\n\n    public static Specification<Payee> isExpenseable(Boolean expenseable) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Payee_.EXPENSEABLE), expenseable);\n    }\n\n    public static Specification<Payee> isIncomeable(Boolean incomeable) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Payee_.INCOMEABLE), incomeable);\n    }\n\n    public static Specification<Payee> buildSpecification(PayeeQueryRequest request, Book book) {\n        Specification<Payee> specification = BookNameNotesEnableSpec.isBook(book);\n        if (StringUtils.hasText(request.getName())) {\n            specification = specification.and(BookNameNotesEnableSpec.nameLike(request.getName()));\n        }\n        if (request.getEnable() != null) {\n            specification = specification.and(BookNameNotesEnableSpec.isEnable(request.getEnable()));\n        }\n        if (request.getExpenseable() != null) {\n            specification = specification.and(isExpenseable(request.getExpenseable()));\n        }\n        if (request.getIncomeable() != null) {\n            specification = specification.and(isIncomeable(request.getIncomeable()));\n        }\n        return specification;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class PayeeUpdateRequest {\n\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    private Boolean expenseable;\n    private Boolean incomeable;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/payee/PayeeVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.payee;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class PayeeVOForList {\n\n    private Integer id;\n    private String name;\n    private String notes;\n    private Boolean enable;\n    private Boolean expenseable;\n    private Boolean incomeable;\n\n    public static PayeeVOForList fromEntity(Payee po) {\n        if (po == null) return null;\n        PayeeVOForList vo = new PayeeVOForList();\n        vo.setId(po.getId());\n        vo.setName(po.getName());\n        vo.setNotes(po.getNotes());\n        vo.setEnable(po.getEnable());\n        vo.setExpenseable(po.getExpenseable());\n        vo.setIncomeable(po.getIncomeable());\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/permission/PermissionCheck.java",
    "content": "package com.jiukuaitech.bookkeeping.user.permission;\n\nimport java.lang.annotation.*;\n\n@Target(ElementType.METHOD)\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface PermissionCheck {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/permission/PermissionCheckAspect.java",
    "content": "package com.jiukuaitech.bookkeeping.user.permission;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.exception.PermissionException;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserGroupRelation;\nimport com.jiukuaitech.bookkeeping.user.user.UserGroupRelationRepository;\nimport com.jiukuaitech.bookkeeping.user.user.UserRepository;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.aspectj.lang.annotation.Pointcut;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.Resource;\nimport javax.servlet.http.HttpServletRequest;\n\n@Aspect\n@Component\npublic class PermissionCheckAspect {\n\n    @Resource\n    private HttpServletRequest request;\n\n    @Resource\n    private UserGroupRelationRepository userGroupRelationRepository;\n\n    @Resource\n    private UserRepository userRepository;\n\n    @Pointcut(\"@annotation(com.jiukuaitech.bookkeeping.user.permission.PermissionCheck)\")\n    private void permissionCheckCut() { };\n\n    @Before(\"permissionCheckCut()\")\n    public void before(JoinPoint joinPoint) {\n        Integer userId = (Integer) request.getAttribute(\"userSignInId\");\n        User user = userRepository.findById(userId).get();\n        Book book = user.getDefaultBook();\n        UserGroupRelation userGroupRelation = userGroupRelationRepository.findOneByUserAndGroup(user, book.getGroup());\n        if (userGroupRelation == null || userGroupRelation.getRole() == 3) {\n            throw new PermissionException(\"No Permission\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/refund/Refund.java",
    "content": "package com.jiukuaitech.bookkeeping.user.refund;\n\nimport com.jiukuaitech.bookkeeping.user.deal.Deal;\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport lombok.Getter;\nimport lombok.NoArgsConstructor;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name=\"t_refund\")\n@Getter\n@Setter\n@NoArgsConstructor\npublic class Refund extends BaseEntity {\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @NotNull\n    private Deal deal;\n\n    @OneToOne(optional = false, fetch = FetchType.LAZY)\n    @NotNull\n    private Deal refund;\n\n    public Refund(Deal deal, Deal refund) {\n        this.deal = deal;\n        this.refund = refund;\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/refund/RefundRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.refund;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport com.jiukuaitech.bookkeeping.user.deal.Deal;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.List;\nimport java.util.Optional;\n\n@Repository\npublic interface RefundRepository extends BaseRepository<Refund, Integer> {\n\n    Optional<Refund> findByRefund(Deal refund);\n\n    List<Refund> findByDeal(Deal deal);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/refund/RefundService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.refund;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.deal.Deal;\nimport com.jiukuaitech.bookkeeping.user.deal.DealRepository;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Service\npublic class RefundService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private DealRepository dealRepository;\n\n    @Resource\n    private RefundRepository refundRepository;\n\n    public List<Integer> getRefunds(Integer dealId, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Deal po = dealRepository.findOneByBookAndId(book, dealId).orElseThrow(ItemNotFoundException::new);\n        // 不需要验证，返回自己，不验证也不会报错。\n//        if (po.getStatus() != 3 && po.getAmount().compareTo(BigDecimal.ZERO) > 0) {\n//            throw new StatusNotValidateException();\n//        }\n        Deal deal;\n        if (po.getAmount().compareTo(BigDecimal.ZERO) > 0) {\n            deal = po;\n        } else {\n            Refund refund = refundRepository.findByRefund(po).orElseThrow(ItemNotFoundException::new);\n            deal = refund.getDeal();\n        }\n        List<Refund> refunds = refundRepository.findByDeal(deal);\n        List<Integer> result = refunds.stream().map(i->i.getRefund().getId()).collect(Collectors.toList());\n        result.add(deal.getId());\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/reports/BreakOutOfMaxException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.reports;\n\npublic class BreakOutOfMaxException extends RuntimeException { }\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/reports/ChartVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.reports;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class ChartVO {\n\n    private String x;\n    private BigDecimal y;\n    private BigDecimal percent;\n\n    public ChartVO() { }\n\n    public ChartVO(String x, BigDecimal y) {\n        this.x = x;\n        this.y = y;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/reports/ChartVO2.java",
    "content": "package com.jiukuaitech.bookkeeping.user.reports;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class ChartVO2 {\n\n    private String x1;\n    private String x2;\n    private BigDecimal y;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/reports/ExpenseIncomeTrendQueryRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.reports;\n\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotEmpty;\nimport javax.validation.constraints.NotNull;\nimport java.util.Set;\n\n@Getter\n@Setter\npublic class ExpenseIncomeTrendQueryRequest extends TrendTimeQueryRequest {\n\n    private Set<Integer> expenseAccounts;\n    private Set<Integer> expensePayees;\n    private Set<Integer> expenseCategories;\n    private Set<Integer> expenseTags;\n\n    private Set<Integer> incomeAccounts;\n    private Set<Integer> incomePayees;\n    private Set<Integer> incomeCategories;\n    private Set<Integer> incomeTags;\n\n    @NotNull\n    @TimeValidator\n    private Long minTime;\n\n    @NotNull\n    @TimeValidator\n    private Long maxTime;\n\n    @NotEmpty\n    private String breakType;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/reports/ReportController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.reports;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.RequestAttribute;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/reports\")\npublic class ReportController extends BaseController {\n\n    @Resource\n    private ReportService reportService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"expense-category\")\n    public BaseResponse handleExpenseCategory(@Valid BalanceFlowQueryRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(reportService.reportExpenseCategory(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"expense-tag\")\n    public BaseResponse handleExpenseTag(@Valid BalanceFlowQueryRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(reportService.reportExpenseTag(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"income-category\")\n    public BaseResponse handleIncomeCategory(@Valid BalanceFlowQueryRequest request,  @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(reportService.reportIncomeCategory(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"income-tag\")\n    public BaseResponse handleIncomeTag(@Valid BalanceFlowQueryRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(reportService.reportIncomeTag(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"expense-income-trend\")\n    public BaseResponse handleTrend(@Valid ExpenseIncomeTrendQueryRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(reportService.reportExpenseIncomeTrend(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"asset\")\n    public BaseResponse handleAsset(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(reportService.reportAsset(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"debt\")\n    public BaseResponse handleDebt(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(reportService.reportDebt(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"asset-debt-trend\")\n    public BaseResponse handleAssetTrend(@Valid TrendTimeQueryRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(reportService.reportAssetDebtTrend(request, userSignInId));\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/reports/ReportService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.reports;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.asset_account.AssetAccountRepository;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.category.Category;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryRepository;\nimport com.jiukuaitech.bookkeeping.user.category_relation.CategoryRelation;\nimport com.jiukuaitech.bookkeeping.user.checking_account.CheckingAccountRepository;\nimport com.jiukuaitech.bookkeeping.user.credit_account.CreditAccountRepository;\nimport com.jiukuaitech.bookkeeping.user.debt_account.DebtAccountRepository;\nimport com.jiukuaitech.bookkeeping.user.expense.Expense;\nimport com.jiukuaitech.bookkeeping.user.expense.ExpenseRepository;\nimport com.jiukuaitech.bookkeeping.user.expense.Expense_;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.income.Income;\nimport com.jiukuaitech.bookkeeping.user.income.IncomeRepository;\nimport com.jiukuaitech.bookkeeping.user.income.Income_;\nimport com.jiukuaitech.bookkeeping.user.tag.Tag;\nimport com.jiukuaitech.bookkeeping.user.tag.TagRepository;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelation;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.account.AccountSpec;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowSpec;\nimport com.jiukuaitech.bookkeeping.user.balance_log.BalanceLog;\nimport com.jiukuaitech.bookkeeping.user.balance_log.BalanceLogRepository;\nimport com.jiukuaitech.bookkeeping.user.currency.CurrencyService;\nimport com.jiukuaitech.bookkeeping.user.deal.DealSpec;\nimport com.jiukuaitech.bookkeeping.user.utils.CalendarUtils;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.MessageSource;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport org.springframework.util.CollectionUtils;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n@Service\npublic class ReportService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private ExpenseRepository expenseRepository;\n\n    @Resource\n    private IncomeRepository incomeRepository;\n\n    @Resource\n    private TagRepository tagRepository;\n\n    @Resource\n    private CheckingAccountRepository checkingAccountRepository;\n\n    @Resource\n    private CreditAccountRepository creditAccountRepository;\n\n    @Resource\n    private DebtAccountRepository debtAccountRepository;\n\n    @Resource\n    private AssetAccountRepository assetAccountRepository;\n\n    @Resource\n    private BalanceLogRepository balanceLogRepository;\n\n    @Resource\n    private CategoryRepository categoryRepository;\n\n    @Resource\n    private CurrencyService currencyService;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @Value(\"${trend.time.max.break}\")\n    private Integer maxBreak;\n\n    public List<ChartVO> reportExpenseCategory(BalanceFlowQueryRequest request, Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Book book = user.getDefaultBook();\n        List<Category> categories = categoryRepository.findAllByBookAndType(book, 1);\n        Category requestCategory = null;\n        List<Category> rootCategories;\n        if (request.getCategoryId() == null) {\n            rootCategories = categories.stream().filter(i->i.getLevel() == 0).collect(Collectors.toList());\n        } else {\n            requestCategory = categories.stream().filter(i-> i.getId().equals(request.getCategoryId())).collect(Collectors.toList()).get(0);\n            rootCategories = requestCategory.getOffspring(categories);\n        }\n        request.setBookId(book.getId());\n        Specification<Expense> specification = DealSpec.buildSpecification(request, group);\n        // 不统计待确认的状态\n        specification = specification.and(BalanceFlowSpec.statusConfirmed());\n        List<Expense> expenses = expenseRepository.findAll(specification);\n        List<CategoryRelation> expenseTos = new ArrayList<>();\n        expenses.forEach(i -> expenseTos.addAll(i.getCategories()));\n        rootCategories.forEach(i -> {\n            ChartVO vo = new ChartVO();\n            vo.setX(i.getName());\n            List<Integer> offSpringCategoryIds = i.getOffspring(categories).stream().map(j->j.getId()).collect(Collectors.toList());\n            offSpringCategoryIds.add(i.getId());\n            vo.setY(expenseTos.stream().filter(j -> offSpringCategoryIds.contains(j.getCategory().getId())).map(CategoryRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            if (vo.getY().compareTo(BigDecimal.ZERO) > 0) {\n                result.add(vo);\n            }\n        });\n        if (request.getCategoryId() != null) {\n            ChartVO vo = new ChartVO();\n            if (rootCategories.size() > 0) {\n                vo.setX(messageSource.getMessage(\"categoryOther\", null, Locale.CHINA));\n            } else { //只有一个分类，不用其他名字\n                vo.setX(requestCategory.getName());\n            }\n            vo.setY(expenseTos.stream().filter(i -> i.getCategory().getId().equals(request.getCategoryId())).map(CategoryRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            if (vo.getY().signum() > 0) {\n                result.add(vo);\n            }\n        }\n        result.sort(Comparator.comparing(ChartVO::getY).reversed());\n        BigDecimal total = result.stream().map(ChartVO::getY).reduce(BigDecimal.ZERO, BigDecimal::add);\n        result.forEach(vo -> vo.setPercent(vo.getY().multiply(new BigDecimal(100)).divide(total, 2, RoundingMode.HALF_UP)));\n        return result;\n    }\n\n    public List<ChartVO> reportExpenseTag(BalanceFlowQueryRequest request, Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Book book = user.getDefaultBook();\n        List<Tag> tags = tagRepository.findByBookAndEnableAndExpenseable(book, true, true);\n        List<Tag> rootTags = new ArrayList<>();\n        List<Integer> requestTags = new ArrayList<>();\n        Tag requestTag = null;\n        if (CollectionUtils.isEmpty(request.getTags())) {\n            rootTags = tags.stream().filter(i->i.getLevel() == 0).collect(Collectors.toList());\n        } else {\n            requestTags.addAll(request.getTags());\n            if (requestTags.size() == 1) {\n                requestTag = tags.stream().filter(i-> i.getId().equals(requestTags.iterator().next())).collect(Collectors.toList()).get(0);\n                rootTags = requestTag.getOffspring(tags);\n                request.getTags().addAll(rootTags.stream().map(j->j.getId()).collect(Collectors.toList()));\n            } else {\n//                for (Integer id : request.getTags()) {\n//                    rootTags.add(tagRepository.findById(id).orElseThrow(ItemNotFoundException::new));\n//                }\n                for (Integer i : requestTags) {\n                    Tag tag = tags.stream().filter(j -> j.getId().equals(i)).collect(Collectors.toList()).get(0);\n                    rootTags.add(tag);\n                    request.getTags().addAll(tag.getOffspring(tags).stream().map(j->j.getId()).collect(Collectors.toList()));\n                }\n            }\n        }\n        request.setBookId(book.getId());\n        Specification<Expense> specification = DealSpec.buildSpecification(request, group);\n        // 不统计待确认的状态\n        specification = specification.and(BalanceFlowSpec.statusConfirmed());\n        List<Expense> expenses = expenseRepository.findAll(specification);\n        List<TagRelation> tagRelations = new ArrayList<>();\n        expenses.forEach(i->tagRelations.addAll(i.getTags()));\n        rootTags.forEach(i -> {\n            ChartVO vo = new ChartVO();\n            vo.setX(i.getName());\n            List<Integer> offSpringIds = i.getOffspring(tags).stream().map(j->j.getId()).collect(Collectors.toList());\n            offSpringIds.add(i.getId());\n            vo.setY(tagRelations.stream().filter(j -> offSpringIds.contains(j.getTag().getId())).map(TagRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            result.add(vo);\n        });\n        if (!CollectionUtils.isEmpty(requestTags) && requestTags.size() == 1) {\n            ChartVO vo = new ChartVO();\n            if (rootTags.size() > 0) {\n                vo.setX(messageSource.getMessage(\"categoryOther\", null, Locale.CHINA));\n            } else { //只有一个分类，不用其他名字\n                vo.setX(requestTag.getName());\n            }\n            Tag finalRequestTag = requestTag;\n            vo.setY(tagRelations.stream().filter(i -> i.getTag().getId().equals(finalRequestTag.getId())).map(TagRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            if (vo.getY().signum() > 0) {\n                result.add(vo);\n            }\n        }\n        return result;\n    }\n\n    public List<ChartVO> reportIncomeCategory(BalanceFlowQueryRequest request, Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Book book = user.getDefaultBook();\n        List<Category> categories = categoryRepository.findAllByBookAndType(book, 2);\n        Category requestCategory = null;\n        List<Category> rootCategories;\n        if (request.getCategoryId() == null) {\n            rootCategories = categories.stream().filter(i->i.getLevel() == 0).collect(Collectors.toList());\n        } else {\n            requestCategory = categories.stream().filter(i-> i.getId().equals(request.getCategoryId())).collect(Collectors.toList()).get(0);\n            rootCategories = requestCategory.getOffspring(categories);\n        }\n        request.setBookId(book.getId());\n        Specification<Income> specification = DealSpec.buildSpecification(request, group);\n        // 不统计待确认的状态\n        specification = specification.and(BalanceFlowSpec.statusConfirmed());\n        List<Income> incomes = incomeRepository.findAll(specification);\n        List<CategoryRelation> incomeFroms = new ArrayList<>();\n        incomes.forEach(i -> incomeFroms.addAll(i.getCategories()));\n        rootCategories.forEach(i -> {\n            ChartVO vo = new ChartVO();\n            vo.setX(i.getName());\n            List<Integer> offSpringCategoryIds = i.getOffspring(categories).stream().map(j->j.getId()).collect(Collectors.toList());\n            offSpringCategoryIds.add(i.getId());\n            vo.setY(incomeFroms.stream().filter(j -> offSpringCategoryIds.contains(j.getCategory().getId())).map(CategoryRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            if (vo.getY().compareTo(BigDecimal.ZERO) > 0) {\n                result.add(vo);\n            }\n        });\n        if (request.getCategoryId() != null) {\n            ChartVO vo = new ChartVO();\n            if (result.size() > 0) {\n                vo.setX(messageSource.getMessage(\"categoryOther\", null, Locale.CHINA));\n            } else { //只有一个分类，不用其他名字\n                vo.setX(requestCategory.getName());\n            }\n            vo.setY(incomeFroms.stream().filter(i -> i.getCategory().getId().equals(request.getCategoryId())).map(CategoryRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            if (vo.getY().compareTo(BigDecimal.valueOf(0)) > 0) {\n                result.add(vo);\n            }\n        }\n        result.sort(Comparator.comparing(ChartVO::getY).reversed());\n        BigDecimal total = result.stream().map(ChartVO::getY).reduce(BigDecimal.ZERO, BigDecimal::add);\n        result.forEach(vo -> vo.setPercent(vo.getY().multiply(new BigDecimal(100)).divide(total, 2, RoundingMode.HALF_UP)));\n        return result;\n    }\n\n    public List<ChartVO> reportIncomeTag(BalanceFlowQueryRequest request, Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Book book = user.getDefaultBook();\n        List<Tag> tags = tagRepository.findByBookAndEnableAndIncomeable(book, true, true);\n        List<Tag> rootTags = new ArrayList<>();\n        List<Integer> requestTags = new ArrayList<>();\n        Tag requestTag = null;\n        if (CollectionUtils.isEmpty(request.getTags())) {\n            rootTags = tags.stream().filter(i->i.getLevel() == 0).collect(Collectors.toList());\n        } else {\n            requestTags.addAll(request.getTags());\n            if (requestTags.size() == 1) {\n                requestTag = tags.stream().filter(i-> i.getId().equals(requestTags.iterator().next())).collect(Collectors.toList()).get(0);\n                rootTags = requestTag.getOffspring(tags);\n                request.getTags().addAll(rootTags.stream().map(j->j.getId()).collect(Collectors.toList()));\n            } else {\n                for (Integer i : requestTags) {\n                    Tag tag = tags.stream().filter(j -> j.getId().equals(i)).collect(Collectors.toList()).get(0);\n                    rootTags.add(tag);\n                    request.getTags().addAll(tag.getOffspring(tags).stream().map(j->j.getId()).collect(Collectors.toList()));\n                }\n            }\n        }\n        request.setBookId(book.getId());\n        Specification<Income> specification = DealSpec.buildSpecification(request, group);\n        // 不统计待确认的状态\n        specification = specification.and(BalanceFlowSpec.statusConfirmed());\n        List<Income> incomes = incomeRepository.findAll(specification);\n        List<TagRelation> tagRelations = new ArrayList<>();\n        incomes.forEach(i->tagRelations.addAll(i.getTags()));\n        rootTags.forEach(i -> {\n            ChartVO vo = new ChartVO();\n            vo.setX(i.getName());\n            List<Integer> offSpringIds = i.getOffspring(tags).stream().map(j->j.getId()).collect(Collectors.toList());\n            offSpringIds.add(i.getId());\n            vo.setY(tagRelations.stream().filter(j -> offSpringIds.contains(j.getTag().getId())).map(TagRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            result.add(vo);\n        });\n        if (!CollectionUtils.isEmpty(requestTags) && requestTags.size() == 1) {\n            ChartVO vo = new ChartVO();\n            if (rootTags.size() > 0) {\n                vo.setX(messageSource.getMessage(\"categoryOther\", null, Locale.CHINA));\n            } else { //只有一个分类，不用其他名字\n                vo.setX(requestTag.getName());\n            }\n            Tag finalRequestTag = requestTag;\n            vo.setY(tagRelations.stream().filter(i -> i.getTag().getId().equals(finalRequestTag.getId())).map(TagRelation::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n            if (vo.getY().signum() > 0) {\n                result.add(vo);\n            }\n        }\n        return result;\n    }\n\n    private String breakTypeToKey(String type, Calendar calendar) {\n        switch (type) {\n            case \"day\":\n                return new SimpleDateFormat(\"yy/MM/dd\").format(calendar.getTime());\n            case \"week\":\n                // TODO 国际化 美国的周日是第一天\n                calendar.setFirstDayOfWeek(Calendar.MONDAY);\n                return calendar.get(Calendar.YEAR) + \"W\" + calendar.get(Calendar.WEEK_OF_YEAR);\n            case \"month\":\n                return new SimpleDateFormat(\"yy/MM\").format(calendar.getTime());\n            case \"quarter\":\n                return calendar.get(Calendar.YEAR) + \"Q\" + ((calendar.get(Calendar.MONTH) / 3) + 1);\n            case \"year\":\n                return String.valueOf(calendar.get(Calendar.YEAR));\n            default:\n                return \"\";\n        }\n    }\n\n    public List<ChartVO2> reportExpenseIncomeTrend(ExpenseIncomeTrendQueryRequest request, Integer userSignInId) {\n        List<ChartVO2> result = new ArrayList<>();\n        User user = userService.getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Book book = user.getDefaultBook();\n        if (request.getMinTime() == null) {\n            request.setMinTime(CalendarUtils.getIn1Year()[0]);\n        }\n        List<Calendar[]> breaks = CalendarUtils.getBreaks(request.getMinTime(), request.getMaxTime(), request.getBreakType());\n        if (breaks.size() > maxBreak) throw new BreakOutOfMaxException();\n        List<ChartVO> expenseMap = new ArrayList<>();\n        BalanceFlowQueryRequest expenseQueryRequest = new BalanceFlowQueryRequest();\n        expenseQueryRequest.setAccounts(request.getExpenseAccounts());\n        expenseQueryRequest.setPayees(request.getExpensePayees());\n        expenseQueryRequest.setCategories(request.getExpenseCategories());\n        expenseQueryRequest.setTags(request.getExpenseTags());\n        expenseQueryRequest.setBookId(book.getId());\n        breaks.forEach(i -> {\n            expenseQueryRequest.setMinTime(i[0].getTimeInMillis());\n            expenseQueryRequest.setMaxTime(i[1].getTimeInMillis());\n            Specification<Expense> specification = DealSpec.buildSpecification(expenseQueryRequest, group);\n            // 不统计待确认的状态\n            specification = specification.and(BalanceFlowSpec.statusConfirmed());\n            BigDecimal amount = expenseRepository.calcAggregate(specification, Expense_.convertedAmount, Expense.class);\n            ChartVO vo = new ChartVO(breakTypeToKey(request.getBreakType(), i[0]), amount);\n            expenseMap.add(vo);\n        });\n        List<ChartVO> incomeMap = new ArrayList<>();\n        BalanceFlowQueryRequest incomeQueryRequest = new BalanceFlowQueryRequest();\n        incomeQueryRequest.setAccounts(request.getIncomeAccounts());\n        incomeQueryRequest.setPayees(request.getIncomePayees());\n        incomeQueryRequest.setCategories(request.getIncomeCategories());\n        incomeQueryRequest.setTags(request.getIncomeTags());\n        incomeQueryRequest.setBookId(book.getId());\n        breaks.forEach(i -> {\n            incomeQueryRequest.setMinTime(i[0].getTimeInMillis());\n            incomeQueryRequest.setMaxTime(i[1].getTimeInMillis());\n            Specification<Income> specification = DealSpec.buildSpecification(incomeQueryRequest, group);\n            // 不统计待确认的状态\n            specification = specification.and(BalanceFlowSpec.statusConfirmed());\n            BigDecimal amount = incomeRepository.calcAggregate(specification, Income_.convertedAmount, Income.class);\n            ChartVO vo = new ChartVO(breakTypeToKey(request.getBreakType(), i[0]), amount);\n            incomeMap.add(vo);\n        });\n        for (int i = 0; i < breaks.size(); i++) {\n            ChartVO2 vo1 = new ChartVO2();\n            vo1.setX1(expenseMap.get(i).getX());\n            vo1.setX2(messageSource.getMessage(\"expense\", null, Locale.CHINA));\n            vo1.setY(expenseMap.get(i).getY());\n            result.add(vo1);\n\n            ChartVO2 vo2 = new ChartVO2();\n            vo2.setX1(incomeMap.get(i).getX());\n            vo2.setX2(messageSource.getMessage(\"income\", null, Locale.CHINA));\n            vo2.setY(incomeMap.get(i).getY());\n            result.add(vo2);\n\n            ChartVO2 vo3 = new ChartVO2();\n            vo3.setX1(incomeMap.get(i).getX());\n            vo3.setX2(messageSource.getMessage(\"remain\", null, Locale.CHINA));\n            vo3.setY(incomeMap.get(i).getY().subtract(expenseMap.get(i).getY()));\n            result.add(vo3);\n        }\n        return result;\n    }\n\n    public List<ChartVO> reportAsset(Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<Account> accounts = new ArrayList<>();\n        accounts.addAll(checkingAccountRepository.findAll(AccountSpec.inGroupAndInclude(group)));\n        accounts.addAll(assetAccountRepository.findAll(AccountSpec.inGroupAndInclude(group)));\n        accounts.forEach(i -> {\n            if (i.getBalance().compareTo(BigDecimal.ZERO) > 0) {\n                ChartVO vo = new ChartVO();\n                vo.setX(i.getName());\n                vo.setY(currencyService.convert(i.getBalance(), i.getCurrencyCode(), group.getDefaultCurrencyCode()));\n                result.add(vo);\n            }\n        });\n        result.sort(Comparator.comparing(ChartVO::getY).reversed());\n        BigDecimal total = result.stream().map(ChartVO::getY).reduce(BigDecimal.ZERO, BigDecimal::add);\n        result.forEach(vo -> vo.setPercent(vo.getY().multiply(new BigDecimal(100)).divide(total, 2, RoundingMode.HALF_UP)));\n        return result;\n    }\n\n    public List<ChartVO> reportDebt(Integer userSignInId) {\n        List<ChartVO> result = new ArrayList<>();\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        List<Account> accounts = new ArrayList<>();\n        accounts.addAll(creditAccountRepository.findAll(AccountSpec.inGroupAndInclude(group)));\n        accounts.addAll(debtAccountRepository.findAll(AccountSpec.inGroupAndInclude(group)));\n        accounts.forEach(i -> {\n            if (i.getBalance().compareTo(BigDecimal.ZERO) < 0) {\n                ChartVO vo = new ChartVO();\n                vo.setX(i.getName());\n                vo.setY(currencyService.convert(i.getBalance(), i.getCurrencyCode(), group.getDefaultCurrencyCode()).negate());\n                result.add(vo);\n            }\n        });\n        result.sort(Comparator.comparing(ChartVO::getY).reversed());\n        BigDecimal total = result.stream().map(ChartVO::getY).reduce(BigDecimal.ZERO, BigDecimal::add);\n        result.forEach(vo -> vo.setPercent(vo.getY().multiply(new BigDecimal(100)).divide(total, 2, RoundingMode.HALF_UP)));\n        return result;\n    }\n\n    public List<ChartVO2> reportAssetDebtTrend(TrendTimeQueryRequest request, Integer userSignInId) {\n        List<ChartVO2> result = new ArrayList<>();\n        Group group = userService.getUser(userSignInId).getDefaultGroup();\n        if (request.getMinTime() == null) {\n            request.setMinTime(CalendarUtils.getIn1Year()[0]);\n        }\n        List<BalanceLog> logs = balanceLogRepository.findByGroupAndCreateTimeBetweenOrderByCreateTime(group, request.getMinTime(), request.getMaxTime());\n        List<ChartVO> assetMap = new ArrayList<>();\n        List<ChartVO> debtMap = new ArrayList<>();\n        logs.forEach(i -> {\n            ChartVO vo1 = new ChartVO();\n            vo1.setX(new SimpleDateFormat(\"yy/MM/dd\").format(i.getCreateTime()));\n            vo1.setY(i.getAsset());\n            assetMap.add(vo1);\n\n            ChartVO vo2 = new ChartVO();\n            vo2.setX(new SimpleDateFormat(\"yy/MM/dd\").format(i.getCreateTime()));\n            vo2.setY(i.getDebt());\n            debtMap.add(vo2);\n        });\n\n        for (int i = 0; i < logs.size(); i++) {\n            ChartVO2 vo1 = new ChartVO2();\n            vo1.setX1(assetMap.get(i).getX());\n            vo1.setX2(messageSource.getMessage(\"asset\", null, Locale.CHINA));\n            vo1.setY(assetMap.get(i).getY());\n            result.add(vo1);\n\n            ChartVO2 vo2 = new ChartVO2();\n            vo2.setX1(debtMap.get(i).getX());\n            vo2.setX2(messageSource.getMessage(\"debt\", null, Locale.CHINA));\n            vo2.setY(debtMap.get(i).getY());\n            result.add(vo2);\n\n            ChartVO2 vo3 = new ChartVO2();\n            vo3.setX1(assetMap.get(i).getX());\n            vo3.setX2(messageSource.getMessage(\"netWorth\", null, Locale.CHINA));\n            vo3.setY(assetMap.get(i).getY().subtract(debtMap.get(i).getY()));\n            result.add(vo3);\n        }\n\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/reports/ReportsExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.reports;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class ReportsExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = BreakOutOfMaxException.class)\n    @ResponseBody\n    public BaseResponse handleException(BreakOutOfMaxException e) {\n        return new ErrorResponse(602, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/reports/TrendTimeQueryRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.reports;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.time.Instant;\n\n@Getter\n@Setter\npublic class TrendTimeQueryRequest {\n\n    private Long minTime;\n    private Long maxTime = Instant.now().toEpochMilli();\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/response/BaseResponse.java",
    "content": "package com.jiukuaitech.bookkeeping.user.response;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class BaseResponse {\n\n    private boolean success;\n\n    public BaseResponse(boolean success) {\n        this.success = success;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/response/DataResponse.java",
    "content": "package com.jiukuaitech.bookkeeping.user.response;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class DataResponse<T> extends BaseResponse {\n\n    private T data;\n\n    public DataResponse() {\n        super(true);\n    }\n\n    public DataResponse(T data) {\n        super(true);\n        this.data = data;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/response/ErrorResponse.java",
    "content": "package com.jiukuaitech.bookkeeping.user.response;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class ErrorResponse extends BaseResponse {\n\n    private Integer errorCode;\n    private String errorMsg;\n\n\n    public ErrorResponse() {\n        super(false);\n    }\n\n    public ErrorResponse(Integer errorCode) {\n        super(false);\n        this.errorCode = errorCode;\n    }\n\n    public ErrorResponse(Integer errorCode, String errorMsg) {\n        super(false);\n        this.errorCode = errorCode;\n        this.errorMsg = errorMsg;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/response/HasNameVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.response;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\n@AllArgsConstructor\npublic class HasNameVO {\n\n    private Integer id;\n    private String name;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/scheduled/Scheduled.java",
    "content": "package com.jiukuaitech.bookkeeping.user.scheduled;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.expense.Expense;\nimport com.jiukuaitech.bookkeeping.user.income.Income;\nimport com.jiukuaitech.bookkeeping.user.transfer.Transfer;\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.TimeValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name=\"t_scheduled\")\n@Getter\n@Setter\npublic class Scheduled extends BaseEntity {\n\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Integer id;\n\n    @Column(length = 16, nullable = false, name = \"name\")\n    @NotNull\n    @NameValidator\n    private String name;\n\n    @Column(nullable = false)\n    @NotNull\n    @TimeValidator\n    private Long startTime;\n\n    @Column(nullable = false)\n    private Integer endType; // 1 永不结束 2 设置结束日期 3 设置执行次数\n\n    @TimeValidator\n    private Long endTime; //结束日期\n\n    private Integer loopTimes; //执行次数\n\n    @Column(nullable = false)\n    private Integer frequencyType;// 1 每天 2 每周 3 每月\n\n    private Integer hour;\n\n    private Integer minute;\n\n    private Boolean autoConfirmed = true;\n\n    @Column(nullable = false)\n    private Integer type; // 1 支出 2 收入 3 转账\n\n    @OneToOne\n    private Expense expense;\n\n    @OneToOne\n    private Income income;\n\n    @OneToOne\n    private Transfer transfer;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/scheduled/ScheduledAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.scheduled;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class ScheduledAddRequest {\n\n    private String name;\n    private Long startTime;\n    private Integer endType;\n    private Long endTime;\n    private Integer loopTimes;\n    private Integer frequencyType;\n    private Integer hour;\n    private Integer minute;\n    private Boolean autoConfirmed;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/scheduled/ScheduledController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.scheduled;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/scheduleds\")\npublic class ScheduledController {\n\n    @Resource\n    private ScheduledService scheduledService;\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(@Valid @RequestBody ScheduledExpenseAddRequest request, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(scheduledService.addExpense(request, userSignInId));\n    }\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/scheduled/ScheduledExpenseAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.scheduled;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class ScheduledExpenseAddRequest extends ScheduledAddRequest {\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/scheduled/ScheduledRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.scheduled;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\n\n@Repository\npublic interface ScheduledRepository extends BaseRepository<Scheduled, Integer> {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/scheduled/ScheduledService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.scheduled;\n\n\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\n\n@Service\npublic class ScheduledService {\n\n    @Resource\n    private ScheduledRepository scheduledRepository;\n\n    public boolean addExpense(ScheduledExpenseAddRequest request, Integer userSignInId) {\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/setting/Setting.java",
    "content": "package com.jiukuaitech.bookkeeping.user.setting;\n\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name = \"t_setting\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"user_id\", \"c_key\"})})\n@Getter\n@Setter\npublic class Setting extends BaseEntity {\n    \n    @Column(name= \"c_key\", length = 16, nullable = false, unique = true)\n    @NotNull\n    @NameValidator\n    private String key;\n    \n    @Column(length = 16, nullable = false)\n    @NotNull\n    @NameValidator\n    private String value;\n    \n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"user_id\")\n    @NotNull\n    private User user;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/Tag.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableEntity;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.util.CollectionUtils;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Entity\n@Table(name = \"t_tag\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"book_id\", \"name\"})})\n@Getter\n@Setter\n/**\n * 账单标签\n */\npublic class Tag extends BookNameNotesEnableEntity {\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    @JoinColumn(name = \"parent_id\")\n    private Tag parent;\n\n    @Column(nullable = false)\n    @NotNull\n    private Integer level;\n    \n    @Column(nullable = false)\n    @NotNull\n    private Boolean expenseable = true; //是否可支出\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean incomeable = true; //是否可收入\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean transferable = true; //是否可转账\n\n    @OneToMany(mappedBy = \"parent\", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)\n    private List<Tag> children = new ArrayList<>();\n\n    public Tag() { }\n\n    public Tag(Integer id) {\n        super.setId(id);\n    }\n\n    public List<Tag> getChildren(List<Tag> tags) {\n        List<Tag> result = new ArrayList<>();\n        for (Tag item : tags) {\n            if (item.getParent() != null && this.getId().equals(item.getParent().getId())) {\n                result.add(item);\n            }\n        }\n        return result;\n    }\n\n    public List<Tag> getOffspring(List<Tag> tags) {\n        List<Tag> result = new ArrayList<>();\n        List<Tag> children = this.getChildren(tags);\n        if (!CollectionUtils.isEmpty(children)) {\n            result.addAll(children);\n            for (Tag item : children) {\n                result.addAll(item.getOffspring(tags));\n            }\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotEmpty;\n\n@Getter\n@Setter\npublic class TagAddRequest {\n\n    private Integer parentId;\n\n    @NotEmpty\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    private Boolean expenseable = false;\n    private Boolean incomeable = false;\n    private Boolean transferable = false;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/tags\")\npublic class TagController extends BaseController {\n\n    @Resource\n    private TagService tagService;\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid TagQueryRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.getAllTree(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/{id}\")\n    public BaseResponse handleGet(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.get(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/enable\")\n    public BaseResponse handleEnable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.getAllEnable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/expenseable\")\n    public BaseResponse handleExpenseable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.getExpenseable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/incomeable\")\n    public BaseResponse handleIncomeable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.getIncomeable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/transferable\")\n    public BaseResponse handleTransferable(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.getTransferable(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody TagAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.add(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}/toggle\")\n    public BaseResponse handleToggle(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(tagService.toggle(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody TagUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.update(id, request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.DELETE, value = \"/{id}\")\n    public BaseResponse handleDelete(@PathVariable(\"id\") Integer id, @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagService.remove(id, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class TagExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = TagHasTransactionException.class)\n    @ResponseBody\n    public BaseResponse handleException(TagHasTransactionException e) {\n        return new ErrorResponse(409, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = TagMaxCountException.class)\n    @ResponseBody\n    public BaseResponse handleException(TagMaxCountException e) {\n        return new ErrorResponse(410, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagHasTransactionException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\n/*\n删除时有账单关联\n */\npublic class TagHasTransactionException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagMaxCountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\npublic class TagMaxCountException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagQueryRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class TagQueryRequest {\n\n    private String name;\n    private Boolean enable;\n    private Boolean expenseable;\n    private Boolean incomeable;\n    private Boolean transferable;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.List;\nimport java.util.Optional;\n\n@Repository\npublic interface TagRepository extends HasBookRepository<Tag> {\n\n    // 添加时判断名称是否重复\n    Optional<Tag> findByBookAndName(Book book, String name);\n\n    List<Tag> findByBookAndEnable(Book book, Boolean enable);\n\n    List<Tag> findByBookAndEnableAndExpenseable(Book book, Boolean enable, Boolean expenseable);\n\n    List<Tag> findByBookAndEnableAndIncomeable(Book book, Boolean enable, Boolean incomeable);\n\n    List<Tag> findByBookAndEnableAndTransferable(Book book, Boolean enable, Boolean transferable);\n\n    long countByBook(Book book);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableSpec;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.category.CategoryLevelException;\nimport com.jiukuaitech.bookkeeping.user.category.ParentCategoryNotEnableException;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.exception.NameExistsException;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelationRepository;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n@Service\npublic class TagService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private TagRepository tagRepository;\n\n    @Resource\n    private TagRelationRepository tagRelationRepository;\n\n    @Value(\"${category.max.level}\")\n    private Integer maxLevel;\n\n    @Value(\"${tag.max.count}\")\n    private Integer maxCount;\n\n    public List<TagTreeVO> getAllTree(TagQueryRequest request, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Specification<Tag> specification = TagSpec.buildSpecification(request, book);\n        List<Tag> entityList = tagRepository.findAll(specification);\n        return TagTreeVO.valueOfList(entityList);\n    }\n\n    public TagTreeVO get(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Tag tag = tagRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        return TagTreeVO.valueOf(tag);\n    }\n\n    public List<TagVOForList> getAllEnable(Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        List<Tag> entityList = tagRepository.findByBookAndEnable(user.getDefaultBook(), true);\n        return entityList.stream().map(TagVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    public List<TagVOForList> getExpenseable(Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        List<Tag> entityList = tagRepository.findByBookAndEnableAndExpenseable(user.getDefaultBook(), true, true);\n        return entityList.stream().map(TagVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    public List<TagVOForList> getIncomeable(Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        List<Tag> entityList = tagRepository.findByBookAndEnableAndIncomeable(user.getDefaultBook(), true, true);\n        return entityList.stream().map(TagVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    public List<TagVOForList> getTransferable(Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        List<Tag> entityList = tagRepository.findByBookAndEnableAndTransferable(user.getDefaultBook(), true, true);\n        return entityList.stream().map(TagVOForList::fromEntity).collect(Collectors.toList());\n    }\n\n    public TagVOForList add(TagAddRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        Tag parent = null;\n        if (request.getParentId() != null) {\n            parent = tagRepository.findOneByBookAndId(book, request.getParentId()).orElseThrow(ItemNotFoundException::new);\n            if (!parent.getEnable()) {\n                throw new ParentCategoryNotEnableException();\n            }\n            if (parent.getLevel().equals(maxLevel-1)) {\n                throw new CategoryLevelException();\n            }\n        }\n        if (tagRepository.countByBook(book) >= maxCount) {\n            throw new TagMaxCountException();\n        }\n        // 不能重复\n        if (tagRepository.findByBookAndName(book, request.getName()).isPresent()) {\n            throw new NameExistsException();\n        }\n        Tag po = new Tag();\n        po.setName(request.getName());\n        po.setNotes(request.getNotes());\n        if (parent == null) {\n            po.setExpenseable(request.getExpenseable());\n            po.setIncomeable(request.getIncomeable());\n            po.setTransferable(request.getTransferable());\n        } else {\n            po.setExpenseable(parent.getExpenseable());\n            po.setIncomeable(parent.getIncomeable());\n            po.setTransferable(parent.getTransferable());\n        }\n        po.setBook(book);\n        po.setParent(parent);\n        if (parent == null) po.setLevel(0);\n        else po.setLevel(parent.getLevel()+1);\n        return TagVOForList.fromEntity(tagRepository.save(po));\n    }\n\n    public TagVOForList remove(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Tag po = tagRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        // 检查关联性，有账单关联的不能删除\n        if (tagRelationRepository.countByTag_id(id) > 0) throw new TagHasTransactionException();\n        tagRepository.delete(po);\n        return TagVOForList.fromEntity(po);\n    }\n\n    public TagVOForList update(Integer id, TagUpdateRequest request, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Tag po = tagRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        if (!po.getName().equals(request.getName())) {\n            if (tagRepository.findByBookAndName(po.getBook(), request.getName()).isPresent()) {\n                throw new NameExistsException();\n            }\n        }\n        if (request.getName() != null) po.setName(request.getName());\n        if (request.getNotes() != null) po.setNotes(request.getNotes());\n\n        Tag parent = null;\n        if (request.getParentId() != null) {\n            parent = tagRepository.findOneByBookAndId(book, request.getParentId()).orElseThrow(ItemNotFoundException::new);\n            if (!parent.getEnable()) {\n                throw new ParentCategoryNotEnableException();\n            }\n            if (parent.getLevel().equals(maxLevel-1)) {\n                throw new CategoryLevelException();\n            }\n        }\n        po.setParent(parent);\n        if (parent == null) po.setLevel(0);\n        else po.setLevel(parent.getLevel()+1);\n\n        if (request.getExpenseable() != null) po.setExpenseable(request.getExpenseable());\n        if (request.getIncomeable() != null) po.setIncomeable(request.getIncomeable());\n        if (request.getTransferable() != null) po.setTransferable(request.getTransferable());\n\n//        List<Tag> entityList = tagRepository.findAll(BookNameNotesEnableSpec.inBook(po.getBook()));\n//        List<Tag> offSpring = po.getOffspring(entityList);\n//        offSpring.add(po);\n//        for (Tag item : offSpring) {\n//            if (request.getExpenseable() != null) item.setExpenseable(request.getExpenseable());\n//            if (request.getIncomeable() != null) item.setIncomeable(request.getIncomeable());\n//            if (request.getTransferable() != null) item.setTransferable(request.getTransferable());\n//        }\n\n        return TagVOForList.fromEntity(tagRepository.save(po));\n    }\n\n    public boolean toggle(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Tag po = tagRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        List<Tag> entityList = tagRepository.findAll(BookNameNotesEnableSpec.isBook(po.getBook()));\n        List<Tag> offSpring = po.getOffspring(entityList);\n        offSpring.add(po);\n        for (Tag item : offSpring) {\n            item.setEnable(!po.getEnable());\n        }\n        tagRepository.saveAll(offSpring);\n        return true;\n    }\n\n}"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport com.jiukuaitech.bookkeeping.user.base.BookNameNotesEnableSpec;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.util.StringUtils;\n\npublic final class TagSpec {\n\n    public static Specification<Tag> isExpenseable(Boolean expenseable) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Tag_.EXPENSEABLE), expenseable);\n    }\n\n    public static Specification<Tag> isIncomeable(Boolean incomeable) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Tag_.INCOMEABLE), incomeable);\n    }\n\n    public static Specification<Tag> isTransferable(Boolean transferable) {\n        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get(Tag_.TRANSFERABLE), transferable);\n    }\n\n    public static Specification<Tag> buildSpecification(TagQueryRequest request, Book book) {\n        Specification<Tag> specification = BookNameNotesEnableSpec.isBook(book);\n        if (StringUtils.hasText(request.getName())) {\n            specification = specification.and(BookNameNotesEnableSpec.nameLike(request.getName()));\n        }\n        if (request.getEnable() != null) {\n            specification = specification.and(BookNameNotesEnableSpec.isEnable(request.getEnable()));\n        }\n        if (request.getExpenseable() != null) {\n            specification = specification.and(isExpenseable(request.getExpenseable()));\n        }\n        if (request.getIncomeable() != null) {\n            specification = specification.and(isIncomeable(request.getIncomeable()));\n        }\n        if (request.getTransferable() != null) {\n            specification = specification.and(isTransferable(request.getIncomeable()));\n        }\n        return specification;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagTreeVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.util.CollectionUtils;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Getter\n@Setter\npublic class TagTreeVO {\n\n    private Integer id;\n    private String name;\n    private String notes;\n    private Boolean enable;\n    private Boolean expenseable;\n    private Boolean incomeable;\n    private Boolean transferable;\n    private List<TagTreeVO> children;\n    private Integer parentId;\n    private String parentName;\n\n    public static List<TagTreeVO> valueOfList(List<Tag> tags) {\n        List<TagTreeVO> treeVOList = new ArrayList<>();\n        for (Tag item : tags) {\n            if (item.getParent() == null) {\n                treeVOList.add(TagTreeVO.valueOf(item, tags));\n            }\n        }\n        return treeVOList;\n    }\n\n    public static TagTreeVO valueOf(Tag tag, List<Tag> tags) {\n        TagTreeVO treeVO = new TagTreeVO();\n        treeVO.setId(tag.getId());\n        treeVO.setName(tag.getName());\n        treeVO.setNotes(tag.getNotes());\n        treeVO.setEnable(tag.getEnable());\n        treeVO.setExpenseable(tag.getExpenseable());\n        treeVO.setIncomeable(tag.getIncomeable());\n        treeVO.setTransferable(tag.getTransferable());\n        treeVO.setParentId(tag.getParent() == null ? null : tag.getParent().getId());\n        treeVO.setParentName(tag.getParent() == null ? null : tag.getParent().getName());\n        if (!CollectionUtils.isEmpty(tag.getChildren(tags))) {\n            for (Tag item : tag.getChildren(tags)) {\n                if (treeVO.getChildren() == null) {\n                    treeVO.setChildren(new ArrayList<>());\n                }\n                treeVO.getChildren().add(valueOf(item, tags));\n            }\n        }\n        return treeVO;\n    }\n\n    public static TagTreeVO valueOf(Tag tag) {\n        TagTreeVO tagTreeVO = new TagTreeVO();\n        tagTreeVO.setId(tag.getId());\n        tagTreeVO.setName(tag.getName());\n        tagTreeVO.setNotes(tag.getNotes());\n        tagTreeVO.setEnable(tag.getEnable());\n        tagTreeVO.setExpenseable(tag.getExpenseable());\n        tagTreeVO.setIncomeable(tag.getIncomeable());\n        tagTreeVO.setTransferable(tag.getTransferable());\n        if (tag.getParent() != null) tagTreeVO.setParentId(tag.getParent().getId());\n        if (tag.getParent() != null) tagTreeVO.setParentName(tag.getParent().getName());\n        return tagTreeVO;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport com.jiukuaitech.bookkeeping.user.validation.NameValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.NotesValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class TagUpdateRequest {\n\n    private Integer parentId;\n\n    @NameValidator\n    private String name;\n\n    @NotesValidator\n    private String notes;\n\n    private Boolean expenseable;\n    private Boolean incomeable;\n    private Boolean transferable;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag/TagVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class TagVOForList {\n\n    private Integer id;\n    private String name;\n    private String notes;\n    private Boolean enable;\n    private Boolean expenseable;\n    private Boolean incomeable;\n    private Boolean transferable;\n    private Integer parentId;\n\n    public static TagVOForList fromEntity(Tag po) {\n        if (po == null) return null;\n        TagVOForList vo = new TagVOForList();\n        vo.setId(po.getId());\n        vo.setName(po.getName());\n        vo.setNotes(po.getNotes());\n        vo.setEnable(po.getEnable());\n        vo.setExpenseable(po.getExpenseable());\n        vo.setIncomeable(po.getIncomeable());\n        vo.setTransferable(po.getTransferable());\n        vo.setParentId(po.getParent() != null ? po.getParent().getId() : 0);\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag_relation/TagRelation.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag_relation;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.tag.Tag;\nimport com.jiukuaitech.bookkeeping.user.transaction.Transaction;\nimport com.jiukuaitech.bookkeeping.user.validation.AmountValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Entity\n@Table(name = \"t_tag_relation\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"transaction_id\", \"tag_id\"})})\n@Getter\n@Setter\npublic class TagRelation extends BaseEntity {\n    \n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @NotNull\n    @JoinColumn(name = \"tag_id\")\n    private Tag tag;\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @NotNull\n    @JoinColumn(name = \"transaction_id\")\n    private Transaction transaction;\n\n    @Column(nullable = false)\n    @NotNull\n    @AmountValidator\n    private BigDecimal amount;\n\n    @Column(nullable = false)\n    @NotNull\n    @AmountValidator\n    private BigDecimal convertedAmount; // 金额\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag_relation/TagRelationController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag_relation;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n\n@RestController\n@RequestMapping(\"/tag-relations\")\npublic class TagRelationController {\n\n    @Resource\n    private TagRelationService tagRelationService;\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody TagRelationUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(tagRelationService.update(id, request, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag_relation/TagRelationRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag_relation;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\n\n@Repository\npublic interface TagRelationRepository extends BaseRepository<TagRelation, Integer> {\n\n    Integer countByTag_id(Integer tagId);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag_relation/TagRelationService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag_relation;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.AmountInvalidateException;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport org.springframework.stereotype.Service;\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\n\n@Service\npublic class TagRelationService {\n\n    @Resource\n    private TagRelationRepository tagRelationRepository;\n\n    @Resource\n    private UserService userService;\n\n    public TagRelationVOForList update(Integer id, TagRelationUpdateRequest request, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        TagRelation po = tagRelationRepository.findById(id).orElseThrow(ItemNotFoundException::new);\n        if (!po.getTransaction().getBook().equals(book)) throw new ItemNotFoundException();\n        if (request.getAmount().compareTo(BigDecimal.ZERO) == 0) throw new AmountInvalidateException();\n        if (request.getConvertedAmount() != null && request.getConvertedAmount().compareTo(BigDecimal.ZERO) == 0) throw new AmountInvalidateException();\n        po.setAmount(request.getAmount());\n        if (request.getConvertedAmount() != null) {\n            po.setConvertedAmount(request.getConvertedAmount());\n        } else {\n            po.setConvertedAmount(request.getAmount());\n        }\n        return TagRelationVOForList.fromEntity(tagRelationRepository.save(po));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag_relation/TagRelationUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag_relation;\n\n\nimport com.jiukuaitech.bookkeeping.user.validation.AmountValidator;\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class TagRelationUpdateRequest {\n\n    @AmountValidator\n    private BigDecimal amount;\n\n    private BigDecimal convertedAmount;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/tag_relation/TagRelationVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.tag_relation;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class TagRelationVOForList {\n\n    private Integer id;\n    private BigDecimal amount;\n    private BigDecimal convertedAmount;\n    private Integer tagId;\n    private String tagName;\n\n    public static TagRelationVOForList fromEntity(TagRelation po) {\n        if (po == null) return null;\n        TagRelationVOForList vo = new TagRelationVOForList();\n        vo.setId(po.getId());\n        vo.setAmount(po.getAmount());\n        vo.setConvertedAmount(po.getConvertedAmount());\n        vo.setTagId(po.getTag().getId());\n        vo.setTagName(po.getTag().getName());\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transaction/Transaction.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transaction;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlow;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelation;\nimport lombok.Getter;\nimport lombok.Setter;\nimport javax.persistence.*;\nimport java.util.HashSet;\nimport java.util.Set;\n\n@Entity\n@Getter\n@Setter\npublic abstract class Transaction extends BalanceFlow {\n\n    @OneToMany(mappedBy = \"transaction\", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)\n    private Set<TagRelation> tags = new HashSet<>();\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transaction/TransactionAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transaction;\n\nimport com.jiukuaitech.bookkeeping.user.tag.Tag;\nimport com.jiukuaitech.bookkeeping.user.utils.CommonUtils;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowAddRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.exception.InputNotValidException;\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n@Getter\n@Setter\npublic class TransactionAddRequest extends BalanceFlowAddRequest {\n\n    private Boolean confirmed = true;\n    private Set<Integer> tags;\n\n    public void copyPrimitive(Transaction po) {\n        super.copyPrimitive(po);\n        po.setStatus(confirmed ? 1 : 2);\n    }\n\n    public void copyTags(Transaction po, List<Tag> tags) {\n        if (!CollectionUtils.isEmpty(getTags())) {\n            // 自动保存关联对象\n            List<Integer> tagIds = tags.stream().map(BaseEntity::getId).collect(Collectors.toList());\n            getTags().forEach(i-> {\n                if (tagIds.contains(i)) {\n                    po.getTags().add(CommonUtils.getTagRelation(i, po));\n                } else {\n                    throw new InputNotValidException();\n                }\n            });\n        }\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transaction/TransactionRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transaction;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface TransactionRepository extends BaseRepository<Transaction, Integer> {\n\n    Long countByAccount_id(Integer accountId);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transaction/TransactionSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transaction;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelation;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowSpec;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelation_;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.util.CollectionUtils;\n\nimport javax.persistence.criteria.*;\nimport java.util.Set;\n\npublic final class TransactionSpec {\n\n    public static<T extends Transaction> Specification<T> tagsIn(Set<Integer> tags) {\n        return (root, query, criteriaBuilder) -> {\n            Join<T, TagRelation> join = root.join(Transaction_.tags);\n            return join.get(TagRelation_.tag).in(tags);\n        };\n    }\n\n    public static<T extends Transaction> Specification<T> buildSpecification(BalanceFlowQueryRequest request, Group group) {\n        Specification<T> specification = BalanceFlowSpec.buildSpecification(request, group);\n        if (!CollectionUtils.isEmpty(request.getTags())) {\n            specification = specification.and(tagsIn(request.getTags()));\n        }\n        return specification;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transaction/TransactionUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transaction;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowUpdateRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.tag.Tag;\nimport com.jiukuaitech.bookkeeping.user.utils.CommonUtils;\nimport com.jiukuaitech.bookkeeping.user.exception.InputNotValidException;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.util.List;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n@Getter\n@Setter\npublic class TransactionUpdateRequest extends BalanceFlowUpdateRequest {\n\n    private Set<Integer> tags;\n\n    public void updateTags(Transaction po, List<Tag> tags) {\n        if (getTags() != null) {\n            CommonUtils.cleanTagRelation(po.getTags(), getTags());\n            List<Integer> tagIds = tags.stream().map(BaseEntity::getId).collect(Collectors.toList());\n            getTags().forEach(i-> {\n                if (tagIds.contains(i)) {\n                    po.getTags().add(CommonUtils.getTagRelation(i, po));\n                } else {\n                    throw new InputNotValidException();\n                }\n            });\n        }\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transaction/TransactionVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transaction;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowVOForExtend;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelationVOForList;\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\n@Getter\n@Setter\npublic class TransactionVOForList extends BalanceFlowVOForExtend {\n\n    private Set<TagRelationVOForList> tags = new HashSet<>();\n\n    public void setValue(Transaction po) {\n        super.setValue(po);\n        setTags(po.getTags().stream().map(TagRelationVOForList::fromEntity).collect(Collectors.toSet()));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/Transfer.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.transaction.Transaction;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\n\n@Entity\n@DiscriminatorValue(value = \"3\")\n@Getter\n@Setter\npublic class Transfer extends Transaction {\n\n    // from 和父类的 account 共用\n\n    @ManyToOne(fetch = FetchType.LAZY)\n    private Account to;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferAddRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.transaction.Transaction;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionAddRequest;\nimport com.jiukuaitech.bookkeeping.user.validation.AmountValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.math.BigDecimal;\n\nimport javax.validation.constraints.NotNull;\nimport javax.validation.constraints.Positive;\n\n@Getter\n@Setter\npublic class TransferAddRequest extends TransactionAddRequest {\n\n    @NotNull\n    private Integer fromId;\n\n    @NotNull\n    private Integer toId;\n\n    @NotNull\n    @AmountValidator\n    @Positive\n    private BigDecimal amount;\n\n    private BigDecimal convertedAmount;\n\n    @Override\n    public void copyPrimitive(Transaction po) {\n        super.copyPrimitive(po);\n        po.setAmount(amount);\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.data.web.PageableDefault;\nimport org.springframework.web.bind.annotation.*;\n\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n@RestController\n@RequestMapping(\"/transfers\")\npublic class TransferController extends BaseController {\n\n    @Resource\n    private TransferService transferService;\n\n    @RequestMapping(method = RequestMethod.POST, value = \"\")\n    public BaseResponse handleAdd(\n            @Valid @RequestBody TransferAddRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(transferService.add(request, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"\")\n    public BaseResponse handleQuery(\n            @Valid BalanceFlowQueryRequest request,\n            @PageableDefault(sort = {\"createTime\", \"id\"}, direction = Sort.Direction.DESC) Pageable page,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(transferService.queryWithDefaultBook(request, page, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/{id}\")\n    public BaseResponse handleUpdate(\n            @PathVariable(\"id\") Integer id,\n            @Valid @RequestBody TransferUpdateRequest request,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new BaseResponse(transferService.update(id, request, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class TransferExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = TransferFromEqualsToException.class)\n    @ResponseBody\n    public BaseResponse handleException(TransferFromEqualsToException e) {\n        return new ErrorResponse(704, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferFromEqualsToException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\n/**\n * 转出和转入的账号相同\n */\npublic class TransferFromEqualsToException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferQueryResultVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport org.springframework.data.domain.Page;\n\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class TransferQueryResultVO {\n\n    private Page<TransferVOForList> result;\n    private BigDecimal total;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.base.HasBookRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface TransferRepository extends HasBookRepository<Transfer> {\n\n    Long countByTo_id(Integer accountId);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.account.AccountRepository;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.AccountInvalidateException;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.StatusNotValidateException;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.tag.TagRepository;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.user.UserService;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLog;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLogRepository;\nimport com.jiukuaitech.bookkeeping.user.user_log.UserActionLogService;\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.CollectionUtils;\n\nimport javax.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.time.Instant;\n\n@Service\npublic class TransferService {\n\n    @Resource\n    private UserService userService;\n\n    @Resource\n    private AccountRepository accountRepository;\n\n    @Resource\n    private TransferRepository transferRepository;\n\n    @Resource\n    private TagRepository tagRepository;\n\n    @Resource\n    private UserActionLogRepository userActionLogRepository;\n\n    @Resource\n    private UserActionLogService userActionLogService;\n\n    @Transactional\n    public boolean add(TransferAddRequest request, Integer userSignInId) {\n        // 转出和转入不能相同\n        if (request.getFromId().equals(request.getToId())) {\n            throw new TransferFromEqualsToException();\n        }\n        User user = userService.getUser(userSignInId);\n\n        // 不能频繁添加，防止用户恶意操作\n        userActionLogService.check(user);\n        Group group = user.getDefaultGroup();\n        Book book = user.getDefaultBook();\n\n        Account fromAccount = accountRepository.findOneByGroupAndId(group, request.getFromId()).orElseThrow(AccountInvalidateException::new);\n        Account toAccount = accountRepository.findOneByGroupAndId(group, request.getToId()).orElseThrow(AccountInvalidateException::new);\n\n        Transfer po = new Transfer();\n        request.copyPrimitive(po);\n        if (request.getConvertedAmount() != null) {\n            po.setConvertedAmount(request.getConvertedAmount());\n        } else {\n            po.setConvertedAmount(request.getAmount());\n        }\n        po.setCreator(user);\n        po.setGroup(user.getDefaultGroup());\n        po.setBook(user.getDefaultBook());\n        request.copyTags(po, tagRepository.findByBookAndEnable(book, true));\n        po.setAccount(fromAccount);\n        po.setTo(toAccount);\n        transferRepository.save(po);\n        // 正常状态才改变账户余额\n        if (request.getConfirmed()) {\n            confirmBalance(po);\n        }\n        userActionLogRepository.save(new UserActionLog(user, 1, Instant.now().toEpochMilli()));\n        return true;\n    }\n\n    // 退款\n    private void refundBalance(Transfer transfer) {\n        Account fromAccount = transfer.getAccount();\n        Account toAccount = transfer.getTo();\n        BigDecimal amount = transfer.getAmount();\n        fromAccount.setBalance(fromAccount.getBalance().add(amount));\n        toAccount.setBalance(toAccount.getBalance().subtract(amount));\n        accountRepository.save(fromAccount);\n        accountRepository.save(toAccount);\n    }\n\n    // 扣款\n    private void confirmBalance(Transfer po) {\n        Account fromAccount = po.getAccount();\n        Account toAccount = po.getTo();\n        BigDecimal amount = po.getAmount();\n        BigDecimal convertedAmount = po.getConvertedAmount();\n        fromAccount.setBalance(fromAccount.getBalance().subtract(amount));\n        toAccount.setBalance(toAccount.getBalance().add(convertedAmount));\n        accountRepository.save(fromAccount);\n        accountRepository.save(toAccount);\n    }\n\n    public TransferQueryResultVO queryWithDefaultBook(BalanceFlowQueryRequest request, Pageable page, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        request.setBookId(user.getDefaultBook().getId());\n        return query(request, page, userSignInId);\n    }\n\n    public TransferQueryResultVO query(BalanceFlowQueryRequest request, Pageable page, Integer userSignInId) {\n        TransferQueryResultVO result = new TransferQueryResultVO();\n        Specification<Transfer> specification = TransferSpec.buildSpecification(request, userService.getUser(userSignInId).getDefaultGroup());\n        Page<Transfer> poPage = transferRepository.findAll(specification, page);\n        Page<TransferVOForList> voPage = poPage.map(transfer -> {\n            TransferVOForList vo = TransferVOForList.fromEntity(transfer);\n            vo.setCurrencyCode(transfer.getAccount().getCurrencyCode());\n            vo.setNeedConvert(!transfer.getAccount().getCurrencyCode().equals(transfer.getTo().getCurrencyCode()));\n            vo.setToCurrencyCode(transfer.getTo().getCurrencyCode());\n            return vo;\n        });\n        result.setResult(voPage);\n        // 解决join查询重复问题，应该考虑子查询\n        if (CollectionUtils.isEmpty(request.getTags())) {\n            result.setTotal(transferRepository.calcAggregate(specification, Transfer_.convertedAmount, Transfer.class));\n        } else {\n            result.setTotal(transferRepository.findAll(specification).stream().map(Transfer::getConvertedAmount).reduce(BigDecimal.ZERO, BigDecimal::add));\n        }\n        return result;\n    }\n\n    @Transactional\n    public boolean remove(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Transfer po = transferRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        // 只有正常状态的需要退款，未确认和已退款不需要\n        if (po.getStatus() == 1) {\n            refundBalance(po);\n        }\n        transferRepository.delete(po);\n        return true;\n    }\n\n    @Transactional\n    public boolean confirm(Integer id, Integer userSignInId) {\n        Book book = userService.getUser(userSignInId).getDefaultBook();\n        Transfer po = transferRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        if (po.getStatus() != 2) {\n            throw new StatusNotValidateException();\n        }\n        confirmBalance(po);\n        po.setStatus(1);\n        transferRepository.save(po);\n        return true;\n    }\n\n    @Transactional\n    public boolean update(Integer id, TransferUpdateRequest request, Integer userSignInId) {\n        User user = userService.getUser(userSignInId);\n        Book book = user.getDefaultBook();\n        Group group = user.getDefaultGroup();\n        Transfer po = transferRepository.findOneByBookAndId(book, id).orElseThrow(ItemNotFoundException::new);\n        if (!po.getAccount().getId().equals(request.getFromId()) || !po.getTo().getId().equals(request.getToId()) ||\n                (request.getAmount() != null && po.getAmount().compareTo(request.getAmount()) != 0) ||\n                (request.getConvertedAmount() != null && po.getConvertedAmount().compareTo(request.getConvertedAmount()) != 0)) {\n            if (po.getStatus() == 1) {\n                refundBalance(po);\n            }\n            Account fromAccount = accountRepository.findOneByGroupAndId(group, request.getFromId()).orElseThrow(AccountInvalidateException::new);\n            Account toAccount = accountRepository.findOneByGroupAndId(group, request.getToId()).orElseThrow(AccountInvalidateException::new);\n            // 转出和转入不能相同\n            if (fromAccount.getId().equals(toAccount.getId())) throw new TransferFromEqualsToException();\n            po.setAccount(fromAccount);\n            po.setTo(toAccount);\n            po.setAmount(request.getAmount());\n            if (request.getConvertedAmount() != null) {\n                if (!fromAccount.getCurrencyCode().equals(toAccount.getCurrencyCode())) {\n                    po.setConvertedAmount(request.getConvertedAmount());\n                }\n            } else {\n                po.setConvertedAmount(request.getAmount());\n            }\n            if (po.getStatus() == 1) {\n                confirmBalance(po);\n            }\n        }\n        request.updatePrimitive(po);\n        request.updateTags(po, tagRepository.findByBookAndEnable(book, true));\n        transferRepository.save(po);\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferSpec.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.account.Account;\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlowQueryRequest;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionSpec;\nimport org.springframework.data.jpa.domain.Specification;\nimport org.springframework.util.CollectionUtils;\n\nimport javax.persistence.criteria.CriteriaBuilder;\nimport java.util.Set;\n\npublic final class TransferSpec {\n\n    public static Specification<Transfer> fromIn(Set<Integer> accounts) {\n        return (root, query, criteriaBuilder) -> {\n            CriteriaBuilder.In<Account> in = criteriaBuilder.in(root.get(Transfer_.account));\n            accounts.forEach(i -> in.value(new Account(i)));\n            return in;\n        };\n    }\n\n    public static Specification<Transfer> toIn(Set<Integer> accounts) {\n        return (root, query, criteriaBuilder) -> {\n            CriteriaBuilder.In<Account> in = criteriaBuilder.in(root.get(Transfer_.to));\n            accounts.forEach(i -> in.value(new Account(i)));\n            return in;\n        };\n    }\n\n    public static Specification<Transfer> buildSpecification(BalanceFlowQueryRequest request, Group group) {\n        Specification<Transfer> specification = TransactionSpec.buildSpecification(request, group);\n        if (!CollectionUtils.isEmpty(request.getFromAccounts())) {\n            specification = specification.and(fromIn(request.getFromAccounts()));\n        }\n        if (!CollectionUtils.isEmpty(request.getToAccounts())) {\n            specification = specification.and(toIn(request.getToAccounts()));\n        }\n        return specification;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferUpdateRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.balance_flow.BalanceFlow;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionUpdateRequest;\nimport com.jiukuaitech.bookkeeping.user.validation.AmountValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotNull;\nimport javax.validation.constraints.Positive;\nimport java.math.BigDecimal;\n\n@Getter\n@Setter\npublic class TransferUpdateRequest extends TransactionUpdateRequest {\n\n    @NotNull\n    private Integer fromId;\n\n    @NotNull\n    private Integer toId;\n\n    @NotNull\n    @AmountValidator\n    @Positive\n    private BigDecimal amount;\n\n    private BigDecimal convertedAmount;\n\n    @Override\n    public void updatePrimitive(BalanceFlow po) {\n        super.updatePrimitive(po);\n//        po.setAmount(amount); //涉及到退款问题，需要在退款之前改变\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/transfer/TransferVOForList.java",
    "content": "package com.jiukuaitech.bookkeeping.user.transfer;\n\nimport com.jiukuaitech.bookkeeping.user.account.AccountVOForExtend;\nimport com.jiukuaitech.bookkeeping.user.transaction.TransactionVOForList;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class TransferVOForList extends TransactionVOForList {\n\n    private AccountVOForExtend from;\n    private AccountVOForExtend to;\n    private Integer fromId;\n    private Integer toId;\n    private String accountName;\n\n    public static TransferVOForList fromEntity(Transfer po) {\n        TransferVOForList vo = new TransferVOForList();\n        vo.setValue(po);\n        vo.setAccountName(po.getAccount().getName() + \" -> \" + po.getTo().getName());\n        vo.setFrom(AccountVOForExtend.fromEntity(po.getAccount()));\n        vo.setTo(AccountVOForExtend.fromEntity(po.getTo()));\n        vo.setFromId(po.getAccount().getId());\n        vo.setToId(po.getTo().getId());\n        return vo;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/InviteCodeErrorException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\npublic class InviteCodeErrorException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/IpNotAllowedException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\npublic class IpNotAllowedException extends Exception {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/OldPasswordErrorException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\npublic class OldPasswordErrorException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/RegisterNameExistsException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\npublic class RegisterNameExistsException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/SessionUserNotFoundException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\npublic class SessionUserNotFoundException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/SessionVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.book.BookVOForList;\nimport com.jiukuaitech.bookkeeping.user.group.GroupVOForList;\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class SessionVO {\n\n    private UserSessionVO userSessionVO;\n    private BookVOForList defaultBook;\n    private GroupVOForList defaultGroup;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/SigninFailedException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\npublic class SigninFailedException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UploadNotImageException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\npublic class UploadNotImageException extends RuntimeException {\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/User.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.validation.*;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.Email;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name = \"t_user\")\n@Getter\n@Setter\npublic class User extends BaseEntity {\n    \n    @Column(length = 16, unique = true, nullable = false)\n    @NotNull\n    @UserNameValidator\n    private String userName;\n    \n    @Column(length = 16)\n    @NameValidator\n    private String nickName;\n    \n    @Column(length = 32, nullable = false)\n    @NotNull\n    @PasswordValidator\n    private String password;\n\n    @Column(length = 16)\n    @NameValidator\n    private String telephone;\n    \n    @Column(length = 32)\n    @Email\n    private String email;\n    \n    @Column(length = 32, nullable = true)\n    private String ip;\n    \n    @Column(length = 64)\n    @AvatarValidator\n    private String avatar;\n    \n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    private Group defaultGroup; //用户默认操作的组\n    \n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    private Book defaultBook; //用户默认操作的账本\n\n    @Column(nullable = false)\n    @NotNull\n    @TimeValidator\n    private Long vipTime; //会员到期日\n\n    // 上次登录ip，时间，哪个国家 设备等信息 TODO\n\n    @Column(nullable = false)\n    @NotNull\n    private Boolean enable = true;\n\n    @Column(nullable = false)\n    @NotNull\n    @TimeValidator\n    private Long registerTime; //注册时间\n    \n    public User() { }\n\n    public User(Integer id) {\n        super.setId(id);\n    }\n\n    public User(String userName, String password) {\n        this.userName = userName;\n        this.password = password;\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserController.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseController;\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.DataResponse;\nimport org.springframework.web.bind.annotation.*;\nimport javax.annotation.Resource;\nimport javax.validation.Valid;\n\n// 登录，注册，注销等\n@RestController\npublic class UserController extends BaseController {\n\n    @Resource\n    private UserService userService;\n    \n    // 登录\n    @RequestMapping(method = RequestMethod.POST, value = \"/signin\")\n    public BaseResponse handleSignin(@Valid @RequestBody UserSignInRequest request) {\n        return new DataResponse<>(userService.signin(request, getRequest(), getResponse()));\n    }\n\n    @RequestMapping(method = RequestMethod.POST, value = \"/signout\")\n    public BaseResponse handleSignout() {\n        return new BaseResponse(userService.signout(getRequest(), getResponse()));\n    }\n    \n    // 注册\n    @RequestMapping(method = RequestMethod.POST, value = \"/register\")\n    public BaseResponse handleRegister(@Valid @RequestBody UserRegisterRequest request) {\n        return new BaseResponse(userService.register(request, getRequest()));\n    }\n\n    // 修改密码\n    @RequestMapping(method = RequestMethod.PUT, value = \"/updatePassword\")\n    public BaseResponse handleUpdatePassword(@RequestAttribute(\"userSignInId\") Integer userSignInId, @Valid @RequestBody UserUpdatePasswordRequest request) {\n        userService.updatePassword(userSignInId, request);\n        userService.signout(getRequest(), getResponse());\n        return new BaseResponse(true);\n    }\n\n    @RequestMapping(method = RequestMethod.GET, value = \"/session\")\n    public BaseResponse handleSession(@RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(userService.getSession(userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/setDefaultBook/{id}\")\n    public BaseResponse handleSetDefaultBook(\n            @PathVariable(\"id\") Integer id,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(userService.setDefaultBook(id, userSignInId));\n    }\n\n    @RequestMapping(method = RequestMethod.PUT, value = \"/setDefaultGroup/{id}\")\n    public BaseResponse handleSetDefaultGroup(\n            @PathVariable(\"id\") Integer id,\n            @RequestAttribute(\"userSignInId\") Integer userSignInId) {\n        return new DataResponse<>(userService.setDefaultGroup(id, userSignInId));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserDisabledException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\npublic class UserDisabledException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class UserExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = RegisterNameExistsException.class)\n    @ResponseBody\n    public BaseResponse handleException(RegisterNameExistsException e) {\n        return new ErrorResponse(701, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = InviteCodeErrorException.class)\n    @ResponseBody\n    public BaseResponse handleException(InviteCodeErrorException e) {\n        return new ErrorResponse(702, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = SigninFailedException.class)\n    @ResponseBody\n    public BaseResponse handleException(SigninFailedException e) {\n        return new ErrorResponse(703, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = UploadNotImageException.class)\n    @ResponseBody\n    public BaseResponse handleException(UploadNotImageException e) {\n        return new ErrorResponse(704, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = SessionUserNotFoundException.class)\n    @ResponseBody\n    public BaseResponse handleException(SessionUserNotFoundException e) {\n        return new ErrorResponse(705, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = OldPasswordErrorException.class)\n    @ResponseBody\n    public BaseResponse handleException(OldPasswordErrorException e) {\n        return new ErrorResponse(706, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n    @ExceptionHandler(value = UserDisabledException.class)\n    @ResponseBody\n    public BaseResponse handleException(UserDisabledException e) {\n        return new ErrorResponse(706, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserGroupRelation.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\nimport javax.validation.constraints.NotNull;\n\n@Entity\n@Table(name=\"t_user_group_relation\", uniqueConstraints = {@UniqueConstraint(columnNames = {\"user_id\", \"group_id\"})})\n@Getter\n@Setter\npublic class UserGroupRelation extends BaseEntity {\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"user_id\")\n    @NotNull\n    private User user;\n    \n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    @JoinColumn(name = \"group_id\")\n    @NotNull\n    private Group group;\n\n    @Column(nullable = false)\n    @NotNull\n    private Integer role;//1 所有者 2维护者 3访客\n    \n    public UserGroupRelation() { }\n    \n    public UserGroupRelation(User user, Group group, Integer role) {\n        this.user = user;\n        this.group = group;\n        this.role = role;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserGroupRelationRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport org.springframework.data.repository.CrudRepository;\nimport org.springframework.stereotype.Repository;\n\nimport java.util.List;\n\n@Repository\npublic interface UserGroupRelationRepository extends CrudRepository<UserGroupRelation, Integer> {\n    \n    UserGroupRelation findOneByUserAndGroup(User user, Group group);\n    \n    List<UserGroupRelation> findByUser(User user);\n\n    Integer deleteByGroup(Group group);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserRegisterRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.validation.PasswordValidator;\nimport com.jiukuaitech.bookkeeping.user.validation.UserNameValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.Email;\n\n@Getter\n@Setter\npublic class UserRegisterRequest {\n    \n    @UserNameValidator\n    private String userName;\n    \n    @PasswordValidator\n    private String password;\n\n    private String inviteCode;\n\n    @Email\n    private String email;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface UserRepository extends BaseRepository<User, Integer> {\n    \n    User findOneByUserName(String userName);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.book.Book;\nimport com.jiukuaitech.bookkeeping.user.group.Group;\nimport com.jiukuaitech.bookkeeping.user.utils.CommonUtils;\nimport com.jiukuaitech.bookkeeping.user.book.BookRepository;\nimport com.jiukuaitech.bookkeeping.user.book.BookVOForList;\nimport com.jiukuaitech.bookkeeping.user.exception.ItemNotFoundException;\nimport com.jiukuaitech.bookkeeping.user.group.GroupRepository;\nimport com.jiukuaitech.bookkeeping.user.group.GroupVOForList;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.http.ResponseCookie;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.util.WebUtils;\n\nimport javax.annotation.Resource;\nimport javax.servlet.ServletContext;\nimport javax.servlet.http.Cookie;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.time.Instant;\nimport java.util.UUID;\n\n@Service\npublic class UserService {\n\n    @Resource\n    private UserRepository userRepository;\n\n    @Resource\n    private GroupRepository groupRepository;\n\n    @Resource\n    private UserGroupRelationRepository userGroupRelationRepository;\n\n    @Resource\n    private BookRepository bookRepository;\n\n    @Resource\n    private ServletContext servletContext;\n\n    @Value(\"${invite.code}\")\n    private String inviteCode;\n\n    public User getUser(Integer userSignInId) {\n        return userRepository.findById(userSignInId).orElseThrow(SessionUserNotFoundException::new);\n    }\n\n    @Transactional\n    public boolean register(UserRegisterRequest request, HttpServletRequest httpServletRequest) {\n        if (StringUtils.hasText(inviteCode) && !inviteCode.equals(request.getInviteCode())) {\n            throw new InviteCodeErrorException();\n        }\n        // 检查用户名是否存在\n        if (userRepository.findOneByUserName(request.getUserName()) != null) {\n            throw new RegisterNameExistsException();\n        }\n        // 密码MD5之后保存\n        User user = new User(request.getUserName(), CommonUtils.encodePassword(request.getPassword()));\n        if (StringUtils.hasText(request.getEmail())) {\n            user.setEmail(request.getEmail());\n        }\n        user.setNickName(request.getUserName());\n        user.setIp(CommonUtils.getRealIP(httpServletRequest));\n        user.setVipTime(Instant.now().toEpochMilli());\n        user.setRegisterTime(Instant.now().toEpochMilli());\n        // 注册之后，默认一个组\n        Group group = new Group();\n        group.setName(\"默认组\");\n        group.setDefaultCurrencyCode(\"CNY\");\n        groupRepository.save(group);\n\n        Book book = new Book();\n        book.setName(\"默认账本\");\n        book.setDefaultCurrencyCode(\"CNY\");\n        book.setGroup(group);\n        bookRepository.save(book);\n\n        user.setDefaultGroup(group);\n        user.setDefaultBook(book);\n        userRepository.save(user);\n\n        group.setDefaultBook(book);\n        group.setCreator(user);\n        groupRepository.save(group);\n\n        UserGroupRelation userGroupRelation = new UserGroupRelation(user, group, 1);\n        userGroupRelationRepository.save(userGroupRelation);\n        return true;\n    }\n\n    public boolean updatePassword(Integer userSignInId, UserUpdatePasswordRequest request) {\n        User user = getUser(userSignInId);\n        if (!user.getPassword().equals(CommonUtils.encodePassword(request.getOldPassword()))) {\n            throw new OldPasswordErrorException();\n        }\n        user.setPassword(CommonUtils.encodePassword(request.getNewPassword()));\n        userRepository.save(user);\n        return true;\n    }\n\n    public UserSignInResponse signin(UserSignInRequest request, HttpServletRequest httpServletRequest, HttpServletResponse response) {\n        UserSignInResponse userSignInResponse = new UserSignInResponse();\n        User user = userRepository.findOneByUserName(request.getUserName());\n        if (user == null || !user.getPassword().equals(CommonUtils.encodePassword(request.getPassword()))) {\n            throw new SigninFailedException();\n        }\n        if (!user.getEnable()) throw new UserDisabledException();\n        // 用户名和密码都正确\n        String token = UUID.randomUUID().toString();\n        servletContext.setAttribute(token, user.getId());\n        userSignInResponse.setToken(token);\n        userSignInResponse.setRemember(request.getRemember());\n        userSignInResponse.setSessionVO(getSession(user.getId()));\n        // set cookie\n        if (request.getRemember()) {\n            Cookie cookie = new Cookie(\"User-Token\", token);\n            cookie.setMaxAge(30 * 24 * 60 * 60);\n//            cookie.setSecure(true);// only https flat\n            cookie.setHttpOnly(true);\n            cookie.setPath(\"/\");\n            response.addCookie(cookie);\n        } else {\n            httpServletRequest.getSession().setAttribute(\"User-Token\", token);\n        }\n        return userSignInResponse;\n    }\n\n    public boolean signout(HttpServletRequest httpServletRequest, HttpServletResponse response) {\n        if (WebUtils.getCookie(httpServletRequest, \"User-Token\") != null) {\n            Cookie deleteServletCookie = new Cookie(\"User-Token\", null);\n            deleteServletCookie.setMaxAge(0);\n            deleteServletCookie.setPath(\"/\");\n            response.addCookie(deleteServletCookie);\n        }\n        httpServletRequest.getSession().removeAttribute(\"User-Token\");\n        return true;\n    }\n\n    public SessionVO getSession(Integer userSignInId) {\n        User user = getUser(userSignInId);\n        SessionVO sessionVO = new SessionVO();\n        sessionVO.setUserSessionVO(UserSessionVO.fromEntity(user));\n        sessionVO.setDefaultBook(BookVOForList.fromEntity(user.getDefaultBook()));\n        sessionVO.setDefaultGroup(GroupVOForList.fromEntity(user.getDefaultGroup()));\n        return sessionVO;\n    }\n\n    public BookVOForList setDefaultBook(Integer id, Integer userSignInId) {\n        User user = getUser(userSignInId);\n        Group group = user.getDefaultGroup();\n        Book po = bookRepository.findOneByGroupAndId(group, id).orElseThrow(ItemNotFoundException::new);\n        group.setDefaultBook(po);\n        user.setDefaultBook(po);\n        groupRepository.save(group);\n        userRepository.save(user);\n        return BookVOForList.fromEntity(po);\n    }\n\n    public Integer setDefaultGroup(Integer id, Integer userSignInId) {\n        User user = getUser(userSignInId);\n        Group po = groupRepository.findById(id).orElseThrow(ItemNotFoundException::new);\n        UserGroupRelation userGroupRelation = userGroupRelationRepository.findOneByUserAndGroup(user, po);\n        if (userGroupRelation == null) {\n            throw new ItemNotFoundException();\n        }\n        user.setDefaultGroup(po);\n        user.setDefaultBook(po.getDefaultBook());\n        userRepository.save(user);\n        return po.getId();\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserSessionVO.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport java.util.Date;\n\n@Getter\n@Setter\npublic class UserSessionVO {\n    \n    private Integer id;\n    private String userName;\n    private String nickName;\n    private String telephone;\n    private String email;\n    private String avatar;\n    private Date lastActiveTime; //上次操作的时间，利用AOP，在每次用户执行操作后更新。可实现用户在操作时，redis的token过期问题。\n    private Long vipTime;\n\n    public static UserSessionVO fromEntity(User user) {\n        UserSessionVO userSessionVO = new UserSessionVO();\n        userSessionVO.setId(user.getId());\n        userSessionVO.setUserName(user.getUserName());\n        userSessionVO.setNickName(user.getNickName());\n        userSessionVO.setTelephone(user.getTelephone());\n        userSessionVO.setEmail(user.getEmail());\n        userSessionVO.setAvatar(user.getAvatar());\n        userSessionVO.setVipTime(user.getVipTime());\n        return userSessionVO;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserSignInRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.validation.constraints.NotEmpty;\n\n@Getter\n@Setter\npublic class UserSignInRequest {\n\n    @NotEmpty\n    private String userName;\n    \n    private String password;\n    \n//    @Max(value = 60*24*60*60)\n//    @Min(value = 0)\n//    private Integer rememberTime = 60*60; //记住登录状态时间，单位，秒。默认为1小时\n    \n    private Boolean remember = false; //是否记住30天\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserSignInResponse.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport lombok.Getter;\nimport lombok.Setter;\n\n@Getter\n@Setter\npublic class UserSignInResponse {\n\n    private String token;\n    private SessionVO sessionVO;\n    private Boolean remember;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user/UserUpdatePasswordRequest.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user;\n\nimport com.jiukuaitech.bookkeeping.user.validation.PasswordValidator;\nimport lombok.Getter;\nimport lombok.Setter;\n\n\n@Getter\n@Setter\npublic class UserUpdatePasswordRequest {\n    \n    @PasswordValidator\n    private String oldPassword;\n\n    @PasswordValidator\n    private String newPassword;\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user_log/FlowMaxCountException.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user_log;\n\npublic class FlowMaxCountException extends RuntimeException {\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user_log/UserActionExceptionHandler.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user_log;\n\nimport com.jiukuaitech.bookkeeping.user.response.BaseResponse;\nimport com.jiukuaitech.bookkeeping.user.response.ErrorResponse;\nimport org.springframework.context.MessageSource;\nimport org.springframework.core.Ordered;\nimport org.springframework.core.annotation.Order;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.ResponseBody;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\n\nimport javax.annotation.Resource;\nimport java.util.Locale;\n\n@RestControllerAdvice\n@Order(Ordered.HIGHEST_PRECEDENCE)\npublic class UserActionExceptionHandler {\n\n    private static final Locale LANG = Locale.CHINA;\n\n    @Resource\n    private MessageSource messageSource;\n\n    @ExceptionHandler(value = FlowMaxCountException.class)\n    @ResponseBody\n    public BaseResponse handleException(FlowMaxCountException e) {\n        return new ErrorResponse(601, messageSource.getMessage(e.getClass().getSimpleName(), null, LANG));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user_log/UserActionLog.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user_log;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseEntity;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport javax.persistence.*;\n\n@Entity\n@Table(name = \"t_user_action_log\")\n@Getter\n@Setter\npublic class UserActionLog extends BaseEntity {\n\n    @ManyToOne(optional = false, fetch = FetchType.LAZY)\n    private User user;\n\n    private Integer type;\n\n    @Column(nullable = false)\n    private Long actionTime;\n\n    public UserActionLog() { }\n\n    public UserActionLog(User user, Integer type, Long actionTime) {\n        this.user = user;\n        this.type = type;\n        this.actionTime = actionTime;\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user_log/UserActionLogRepository.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user_log;\n\nimport com.jiukuaitech.bookkeeping.user.base.BaseRepository;\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport org.springframework.stereotype.Repository;\n\n@Repository\npublic interface UserActionLogRepository extends BaseRepository<UserActionLog, Integer> {\n\n    long countByUserAndTypeAndActionTimeBetween(User user, int type, long start, long end);\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/user_log/UserActionLogService.java",
    "content": "package com.jiukuaitech.bookkeeping.user.user_log;\n\nimport com.jiukuaitech.bookkeeping.user.user.User;\nimport com.jiukuaitech.bookkeeping.user.utils.CalendarUtils;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\n\n@Service\npublic class UserActionLogService {\n\n    @Resource\n    private UserActionLogRepository userActionLogRepository;\n\n    @Value(\"${flow.max.count.daily}\")\n    private Integer maxCount;\n\n    public boolean check(User user) {\n        Long[] day = CalendarUtils.getIn1Day();\n        if (userActionLogRepository.countByUserAndTypeAndActionTimeBetween(user, 1, day[0], day[1]) >= maxCount) {\n            throw new FlowMaxCountException();\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/utils/CalendarUtils.java",
    "content": "package com.jiukuaitech.bookkeeping.user.utils;\n\nimport java.util.ArrayList;\nimport java.util.Calendar;\nimport java.util.List;\n\npublic class CalendarUtils {\n\n    public static void setToStartOfDay(Calendar c) {\n        c.set(Calendar.HOUR_OF_DAY, 0);\n        c.set(Calendar.MINUTE, 0);\n        c.set(Calendar.SECOND, 0);\n        c.set(Calendar.MILLISECOND, 0);\n    }\n\n    public static void setToEndOfDay(Calendar c) {\n        c.set(Calendar.HOUR_OF_DAY, 23);\n        c.set(Calendar.MINUTE, 59);\n        c.set(Calendar.SECOND, 59);\n        c.set(Calendar.MILLISECOND, 999);\n    }\n\n    public static void setToStartOfWeek(Calendar c) {\n        setToStartOfDay(c);\n        // TODO 国际化，美国的星期一是第一天，或者改为可设置\n//        c.setFirstDayOfWeek(Calendar.SUNDAY);\n        c.setFirstDayOfWeek(Calendar.MONDAY);\n        c.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);\n    }\n\n    public static void setToEndOfWeek(Calendar c) {\n        setToEndOfDay(c);\n        // TODO 国际化，美国的星期一是第一天，或者改为可设置\n        c.setFirstDayOfWeek(Calendar.MONDAY);\n        c.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);\n    }\n\n    public static void setToStartOfMonth(Calendar c) {\n        setToStartOfDay(c);\n        c.set(Calendar.DAY_OF_MONTH, 1);\n    }\n    public static void setToEndOfMonth(Calendar c) {\n        setToEndOfDay(c);\n        c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));\n    }\n\n    public static void setToStartOfQuarter(Calendar c) {\n        setToStartOfDay(c);\n        c.set(Calendar.DAY_OF_MONTH, 1);\n        c.set(Calendar.MONTH, c.get(Calendar.MONTH)/3 * 3);\n    }\n    public static void setToEndOfQuarter(Calendar c) {\n        setToEndOfDay(c);\n        c.set(Calendar.DAY_OF_MONTH, 1);\n        c.set(Calendar.MONTH, c.get(Calendar.MONTH)/3 * 3 + 2);\n        c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));\n    }\n\n    public static void setToStartOfYear(Calendar c) {\n        setToStartOfDay(c);\n        c.set(Calendar.DAY_OF_YEAR, 1);\n    }\n\n    public static void setToEndOfYear(Calendar c) {\n        setToEndOfDay(c);\n        c.set(Calendar.DAY_OF_YEAR, c.getActualMaximum(Calendar.DAY_OF_YEAR));\n    }\n\n    public static Long getStartOfDay(Long time) {\n        Calendar c = Calendar.getInstance();\n        c.setTimeInMillis(time);\n        setToStartOfDay(c);\n        return c.getTimeInMillis();\n    }\n\n    public static Long getEndOfDay(Long time) {\n        Calendar c = Calendar.getInstance();\n        c.setTimeInMillis(time);\n        setToEndOfDay(c);\n        return c.getTimeInMillis();\n    }\n\n    public static Long[] getThisWeek() {\n        Calendar start = Calendar.getInstance();\n        start.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);\n        setToStartOfDay(start);\n        Calendar end = Calendar.getInstance();\n        end.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);\n        setToEndOfDay(end);\n        return new Long[]{ start.getTimeInMillis(), end.getTimeInMillis() };\n    }\n\n    public static Long[] getThisMonth() {\n        Calendar start = Calendar.getInstance();\n        start.set(Calendar.DAY_OF_MONTH, 1);\n        setToStartOfDay(start);\n        Calendar end = Calendar.getInstance();\n        end.set(Calendar.DAY_OF_MONTH, end.getActualMaximum(Calendar.DAY_OF_MONTH));\n        setToEndOfDay(end);\n        return new Long[]{ start.getTimeInMillis(), end.getTimeInMillis() };\n    }\n\n    public static Long[] getThisYear() {\n        Calendar start = Calendar.getInstance();\n        start.set(Calendar.DAY_OF_YEAR, 1);\n        setToStartOfDay(start);\n        Calendar end = Calendar.getInstance();\n        end.set(Calendar.DAY_OF_YEAR, end.getActualMaximum(Calendar.DAY_OF_YEAR));\n        setToEndOfDay(end);\n        return new Long[]{ start.getTimeInMillis(), end.getTimeInMillis() };\n    }\n\n    public static Long[] getLastYear() {\n        Calendar start = Calendar.getInstance();\n        start.set(Calendar.DAY_OF_YEAR, 1);\n        setToStartOfDay(start);\n        Calendar end = Calendar.getInstance();\n        end.set(Calendar.DAY_OF_YEAR, end.getActualMaximum(Calendar.DAY_OF_YEAR));\n        setToEndOfDay(end);\n        start.add(Calendar.YEAR, -1);\n        end.add(Calendar.YEAR, -1);\n        return new Long[]{ start.getTimeInMillis(), end.getTimeInMillis() };\n    }\n\n    public static Long[] getIn7Days() {\n        Calendar start = Calendar.getInstance();\n        start.add(Calendar.DATE, -7);\n        setToStartOfDay(start);\n        Calendar end = Calendar.getInstance();\n        setToEndOfDay(end);\n        return new Long[]{ start.getTimeInMillis(), end.getTimeInMillis() };\n    }\n\n    public static Long[] getIn1Day() {\n        Calendar start = Calendar.getInstance();\n        start.add(Calendar.HOUR, -24);\n        Calendar end = Calendar.getInstance();\n        return new Long[]{ start.getTimeInMillis(), end.getTimeInMillis() };\n    }\n\n    public static Long[] getIn30Days() {\n        Calendar start = Calendar.getInstance();\n        start.add(Calendar.DATE, -30);\n        setToStartOfDay(start);\n        Calendar end = Calendar.getInstance();\n        setToEndOfDay(end);\n        return new Long[]{ start.getTimeInMillis(), end.getTimeInMillis() };\n    }\n\n    public static Long[] getIn1Year() {\n        Calendar start = Calendar.getInstance();\n        start.add(Calendar.YEAR, -1);\n        setToStartOfDay(start);\n        Calendar end = Calendar.getInstance();\n        setToEndOfDay(end);\n        return new Long[]{ start.getTimeInMillis(), end.getTimeInMillis() };\n    }\n\n    public static List<Calendar[]> getMonths(Integer year) {\n        List<Calendar[]> result = new ArrayList<>();\n        Calendar calendar = Calendar.getInstance();\n        if (year < 1970 || year > calendar.get(Calendar.YEAR)) {\n            return result;\n        }\n        if (year == calendar.get(Calendar.YEAR)) {\n            calendar.add(Calendar.MONTH, -11);\n        } else {\n            calendar.set(Calendar.YEAR, year);\n            calendar.set(Calendar.MONTH, 0);\n        }\n        for (int i = 0; i < 12; i++) {\n            Calendar start = (Calendar) calendar.clone();\n            start.set(Calendar.DAY_OF_MONTH, 1);\n            setToStartOfDay(start);\n            Calendar end = (Calendar) calendar.clone();\n            end.set(Calendar.DAY_OF_MONTH, end.getActualMaximum(Calendar.DAY_OF_MONTH));\n            setToEndOfDay(end);\n            result.add(new Calendar[]{ start, end });\n            calendar.add(Calendar.MONTH, 1);\n        }\n        return result;\n    }\n\n    public static List<Calendar[]> getBreaks(Long start, Long end, String type) {\n        List<Calendar[]> result = new ArrayList<>();\n        Calendar startCalendar = Calendar.getInstance();\n        startCalendar.setTimeInMillis(start);\n        setToStartOfDay(startCalendar);\n        Calendar endCalendar = Calendar.getInstance();\n        endCalendar.setTimeInMillis(end);\n        setToEndOfDay(endCalendar);\n        switch (type) {\n            case \"day\":\n                while (startCalendar.getTimeInMillis() < endCalendar.getTimeInMillis()) {\n                    Calendar c1 = (Calendar) startCalendar.clone();\n                    Calendar c2 = (Calendar) startCalendar.clone();\n                    setToEndOfDay(c2);\n                    result.add(new Calendar[]{ c1, c2 });\n                    startCalendar.add(Calendar.DATE, 1);\n                }\n                break;\n            case \"week\":\n                setToStartOfWeek(startCalendar);\n                while (startCalendar.getTimeInMillis() < endCalendar.getTimeInMillis()) {\n                    Calendar c1 = (Calendar) startCalendar.clone();\n                    Calendar c2 = (Calendar) startCalendar.clone();\n                    setToEndOfWeek(c2);\n                    result.add(new Calendar[]{ c1, c2 });\n                    startCalendar.add(Calendar.WEEK_OF_YEAR, 1);\n                }\n                break;\n            case \"month\":\n                setToStartOfMonth(startCalendar);\n                while (startCalendar.getTimeInMillis() < endCalendar.getTimeInMillis()) {\n                    Calendar c1 = (Calendar) startCalendar.clone();\n                    Calendar c2 = (Calendar) startCalendar.clone();\n                    setToEndOfMonth(c2);\n                    result.add(new Calendar[]{ c1, c2 });\n                    startCalendar.add(Calendar.MONTH, 1);\n                }\n                break;\n            case \"quarter\":\n                setToStartOfQuarter(startCalendar);\n                while (startCalendar.getTimeInMillis() < endCalendar.getTimeInMillis()) {\n                    Calendar c1 = (Calendar) startCalendar.clone();\n                    Calendar c2 = (Calendar) startCalendar.clone();\n                    setToEndOfQuarter(c2);\n                    result.add(new Calendar[]{ c1, c2 });\n                    startCalendar.add(Calendar.MONTH, 3);\n                }\n                break;\n            case \"year\":\n                setToStartOfYear(startCalendar);\n                while (startCalendar.getTimeInMillis() < endCalendar.getTimeInMillis()) {\n                    Calendar c1 = (Calendar) startCalendar.clone();\n                    Calendar c2 = (Calendar) startCalendar.clone();\n                    setToEndOfYear(c2);\n                    result.add(new Calendar[]{ c1, c2 });\n                    startCalendar.add(Calendar.YEAR, 1);\n                }\n                break;\n        }\n        return result;\n    }\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/utils/CommonUtils.java",
    "content": "package com.jiukuaitech.bookkeeping.user.utils;\n\nimport com.jiukuaitech.bookkeeping.user.tag.Tag;\nimport com.jiukuaitech.bookkeeping.user.tag_relation.TagRelation;\nimport com.jiukuaitech.bookkeeping.user.transaction.Transaction;\nimport org.springframework.util.DigestUtils;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.xml.bind.DatatypeConverter;\nimport java.math.BigDecimal;\nimport java.text.SimpleDateFormat;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\npublic class CommonUtils {\n\n    public static List<BigDecimal> coalesce(BigDecimal... items) {\n        return Arrays.stream(items).map(i -> i == null ? BigDecimal.valueOf(0) :i ).collect(Collectors.toList());\n    }\n\n    public static void cleanTagRelation(Set<TagRelation> tagRelations, Set<Integer> requestTagIds) {\n        Set<Integer> entityTagIds = tagRelations.stream().map(i->i.getTag().getId()).collect(Collectors.toSet());\n        List<Integer> existsIds = new ArrayList<>();\n        List<TagRelation> toBeRemoved = new ArrayList<>();\n        requestTagIds.forEach(i -> {\n            if (entityTagIds.contains(i)) {\n                existsIds.add(i);\n            }\n        });\n        tagRelations.forEach(j -> {\n            if (!requestTagIds.contains(j.getTag().getId())) {\n                toBeRemoved.add(j);\n            }\n        });\n        tagRelations.removeAll(toBeRemoved);\n        requestTagIds.removeAll(existsIds);\n    }\n\n    public static TagRelation getTagRelation(Integer tagId, Transaction transaction) {\n        TagRelation po = new TagRelation();\n        po.setAmount(transaction.getAmount());\n        po.setConvertedAmount(transaction.getConvertedAmount());\n        po.setTransaction(transaction);\n        po.setTag(new Tag(tagId));\n        return po;\n    }\n\n    /**\n     * 获取用户真实IP地址，不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,\n     * 可是，如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值\n     *\n     * @return ip\n     */\n    public static String getRealIP(HttpServletRequest request) {\n        String ip = request.getHeader(\"x-forwarded-for\");\n        if (ip != null && ip.length() != 0 && !\"unknown\".equalsIgnoreCase(ip)) {\n            // 多次反向代理后会有多个ip值，第一个ip才是真实ip\n            if( ip.indexOf(\",\")!=-1 ){\n                ip = ip.split(\",\")[0];\n            }\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"Proxy-Client-IP\");\n            System.out.println(\"Proxy-Client-IP ip: \" + ip);\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"WL-Proxy-Client-IP\");\n            System.out.println(\"WL-Proxy-Client-IP ip: \" + ip);\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"HTTP_CLIENT_IP\");\n            System.out.println(\"HTTP_CLIENT_IP ip: \" + ip);\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"HTTP_X_FORWARDED_FOR\");\n            System.out.println(\"HTTP_X_FORWARDED_FOR ip: \" + ip);\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = request.getHeader(\"X-Real-IP\");\n            System.out.println(\"X-Real-IP ip: \" + ip);\n        }\n        if (ip == null || ip.length() == 0 || \"unknown\".equalsIgnoreCase(ip)) {\n            ip = request.getRemoteAddr();\n            System.out.println(\"getRemoteAddr ip: \" + ip);\n        }\n        return ip;\n    }\n\n    public static String encodePassword(String str) {\n        byte[] digest = DigestUtils.md5Digest(str.getBytes());\n        return DatatypeConverter.printHexBinary(digest);\n    }\n\n    public static String formatDate(Long date) {\n        SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd\");\n        return sdf.format(new Date(date));\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/utils/EnumUtils.java",
    "content": "package com.jiukuaitech.bookkeeping.user.utils;\n\npublic class EnumUtils {\n\n    public static String translateFlowType(int value) {\n        switch (value) {\n            case 1:\n                return \"支出\";\n            case 2:\n                return \"收入\";\n            case 3:\n                return \"转账\";\n            case 4:\n                return \"余额调整\";\n        }\n        return \"未知\";\n    }\n\n    public static String translateFlowStatus(int value) {\n        switch (value) {\n            case 1:\n                return \"正常\";\n            case 2:\n                return \"待确认\";\n            case 3:\n                return \"已退款\";\n        }\n        return \"未知\";\n    }\n\n    public static String translateAccountType(int value) {\n        switch (value) {\n            case 1:\n                return \"活期账户\";\n            case 2:\n                return \"信用账户\";\n            case 3:\n                return \"贷款账户\";\n            case 4:\n                return \"资产账户\";\n        }\n        return \"未知\";\n    }\n\n    public static String translateItemRepeatType(int type) {\n        switch (type) {\n            case 1:\n                return \"天\";\n            case 2:\n                return \"月\";\n            case 3:\n                return \"年\";\n        }\n        return \"未知\";\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/AmountAccumulateValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Digits;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n/*\n累积统计的数额限制，比如统计流水的数额。\n */\n@Digits(integer = 14, fraction = 2)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface AmountAccumulateValidator {\n    \n    String message() default \"{javax.validation.constraints.Digits.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/AmountValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Digits;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n/*\n每一笔交易的数额限制\n */\n@Digits(integer = 9, fraction = 2)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface AmountValidator {\n    \n    String message() default \"{javax.validation.constraints.Digits.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/AprValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Digits;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n/*\n每一笔交易的数额限制\n */\n@Digits(integer = 3, fraction = 2)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface AprValidator {\n    \n    String message() default \"{javax.validation.constraints.Digits.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/AvatarValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Size;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n\n@Size(max = 128, message = \"notes size must be less than 128\")\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface AvatarValidator {\n    \n    String message() default \"{javax.validation.constraints.Size.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/BalanceValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Digits;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n/*\n账户的余额\n */\n@Digits(integer = 9, fraction = 2)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface BalanceValidator {\n    \n    String message() default \"{javax.validation.constraints.Digits.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/BillDayValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n/*\n信用账户，还款日限制\n */\n@Min(1)\n@Max(31)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface BillDayValidator {\n    \n    String message() default \"{javax.validation.constraints.Digits.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/CreditLimitValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Digits;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n/*\n信用账户，信用额度限制\n */\n@Digits(integer = 9, fraction = 2)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface CreditLimitValidator {\n    \n    String message() default \"{javax.validation.constraints.Digits.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/DescriptionValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Size;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n\n@Size(max = 16, message = \"description size must be less than 16\")\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface DescriptionValidator {\n    \n    String message() default \"{javax.validation.constraints.Size.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/NameValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Size;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n\n@Size(max = 16, message = \"name size must be between 0 and 16\")\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface NameValidator {\n    \n    String message() default \"{javax.validation.constraints.Size.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/NoValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Size;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n\n@Size(max = 32, message = \"no size must be between 0 and 32\")\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface NoValidator {\n    \n    String message() default \"{javax.validation.constraints.Size.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/NotesValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Size;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n\n@Size(max = 1024, message = \"notes size must be less than 1024\")\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface NotesValidator {\n    \n    String message() default \"{javax.validation.constraints.Size.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/PasswordValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Pattern;\nimport javax.validation.constraints.Size;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n\n@NotBlank(message = \"password must not be blank\")\n@Size(min = 6, max = 32, message = \"password size must be between 6 and 32\")\n@Pattern(regexp = \"^[A-Za-z0-9~`!@#\\\\$%\\\\^&\\\\*\\\\(\\\\)-_=\\\\+\\\\[\\\\]\\\\{\\\\}\\\\|]*$\") //数字，字母，特殊字符(不包括空格)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface PasswordValidator {\n    \n    String message() default \"{javax.validation.constraints.Pattern.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/TimeValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Min;\nimport javax.validation.constraints.Max;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n@Min(0)\n@Max(99999999999999L)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface TimeValidator {\n    \n    String message() default \"{javax.validation.constraints.Min.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/TransactionStatusValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.Max;\nimport javax.validation.constraints.Min;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n@Min(1)\n@Max(4)\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface TransactionStatusValidator {\n    \n    String message() default \"{javax.validation.constraints.Digits.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/java/com/jiukuaitech/bookkeeping/user/validation/UserNameValidator.java",
    "content": "package com.jiukuaitech.bookkeeping.user.validation;\n\nimport javax.validation.Constraint;\nimport javax.validation.Payload;\nimport javax.validation.constraints.NotBlank;\nimport javax.validation.constraints.Pattern;\nimport javax.validation.constraints.Size;\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\n\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n\n@NotBlank(message = \"username must not be blank\")\n@Size(min = 3, max = 20, message = \"username size must be between 3 and 20\")\n@Pattern(regexp = \"^[A-Za-z0-9]*$\") //用户名只允许数字加字母\n\n@Target({ FIELD })\n@Retention(RUNTIME)\n@Constraint(validatedBy = { })\n@Documented\npublic @interface UserNameValidator {\n    \n    String message() default \"{javax.validation.constraints.Size.message}\";\n\n    Class<?>[] groups() default { };\n\n    Class<? extends Payload>[] payload() default { };\n    \n}\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/resources/application.properties",
    "content": "spring.datasource.dialect=org.hibernate.dialect.MySQL8InnoDBDialect\nspring.datasource.url=jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true\nspring.datasource.username=${DB_USER}\nspring.datasource.password=${DB_PASSWORD}\nspring.datasource.type=com.zaxxer.hikari.HikariDataSource\n\nspring.datasource.hikari.minimumIdle=5\nspring.datasource.hikari.maximumPoolSize=20\nspring.datasource.hikari.idleTimeout=30000\nspring.datasource.hikari.poolName=BookkeepingUserJPAHikariCP\nspring.datasource.hikari.maxLifetime=2000000\nspring.datasource.hikari.connectionTimeout=30000\n\nspring.jpa.hibernate.ddl-auto=update\nspring.jpa.show-sql=false\nspring.jpa.properties.hibernate.generate_statistics=false\nspring.jpa.properties.hibernate.show_sql=false\nspring.jpa.properties.hibernate.format_sql=true\nspring.mvc.throw-exception-if-no-handler-found=true\n\nspring.jpa.properties.hibernate.jdbc.batch_size = 20\nspring.jpa.properties.hibernate.order_inserts = true\nspring.jpa.properties.hibernate.order_updates = true\nspring.jpa.properties.hibernate.jdbc.batch_versioned_data = true\nspring.jpa.properties.hibernate.validator.apply_to_ddl = true\nspring.jpa.properties.hibernate.check_nullability = true\n\nserver.port=${SERVER_PORT:9092}\nspring.mvc.servlet.path=/api/v1\n\nspring.data.web.pageable.size-parameter=size\nspring.data.web.pageable.page-parameter=page\nspring.data.web.pageable.default-page-size=10\nspring.data.web.pageable.one-indexed-parameters=true\nspring.data.web.pageable.max-page-size=100\nspring.data.web.pageable.prefix=\nspring.data.web.pageable.qualifier-delimiter=_\n\nspring.messages.encoding=UTF-8\nspring.messages.basename=i18n/messages\nspring.messages.cache-duration=3600\n\nspring.devtools.add-properties=false\n#logging.level.web=DEBUG\n\n\nupload.ACCESS_KEY = ${UPLOAD_ACCESS_KEY:}\nupload.SECRET_KEY = ${UPLOAD_SECRET_KEY:}\nupload.FLOW_IMAGE_BUCKET = ${UPLOAD_FLOW_IMAGE_BUCKET:}\nupload.FLOW_IMAGE_HOST = ${UPLOAD_FLOW_IMAGE_HOST:}\nupload.FLOW_IMAGE_CALL_BACK_URL = ${UPLOAD_FLOW_IMAGE_CALL_BACK_URL:}\ninvite.code = ${INVITE_CODE:}\n\n\n# 自定义\n# 类别的最大分层级数\ncategory.max.level = 4\n# 收支趋势时间范围的最大划分区间个数\ntrend.time.max.break = 370\n# 单个用户添加组，最多个数\ngroup.max.count = 5\nbook.max.count = 200\naccount.max.count = 500\ncategory.max.count = 200\ntag.max.count = 300\npayee.max.count = 300\nflow.max.count.daily = 400\n\n\n\n## MULTIPART (MultipartProperties)\n# Enable multipart uploads\nspring.servlet.multipart.enabled=true\n# Threshold after which files are written to disk.\nspring.servlet.multipart.file-size-threshold=2KB\n# Max file size.\nspring.servlet.multipart.max-file-size=10MB\n# Max Request Size\nspring.servlet.multipart.max-request-size=20MB\n\n\n\n\n\n\n\n"
  },
  {
    "path": "bookkeeping-user-api/src/main/resources/i18n/messages.properties",
    "content": "DefaultException=网络错误，请稍后重试\n\nHttpRequestMethodNotSupportedException=HttpRequestMethodNotSupportedException\nHttpMediaTypeNotSupportedException=HttpMediaTypeNotSupportedException\nNoHandlerFoundException=Not Found\nMethodArgumentNotValidException=输入不合法\n\nDataIntegrityViolationException=系统异常，请稍后重试。\nConstraintViolationException=ConstraintViolationException\nMissingServletRequestParameterException=MissingServletRequestParameterException\n\nItemNotFoundException=记录不存在\nNameExistsException=名称重复\n\n# Account\nAccountNameExistsException=操作失败，账户名称重复。\nAccountHasTransactionException=该账户下有账单记录，无法删除。\nAccountAdjustBalanceNotValidException=调整前后余额一样\nDefaultExpenseAccountException=此账户为默认支出账户，无法禁用。\nDefaultIncomeAccountException=此账户为默认收入账户，无法禁用。\nDefaultTransferFromAccountException=此账户为默认转出账户，无法禁用。\nDefaultTransferToAccountException=此账户为默认转入账户，无法禁用。\nAccountMaxCountException=最多添加500个账户\n\n#BalanceFlow\nCategoryConflictException=分类重复\nCategoryLevelException=分类的层级不能超过4\nBalanceFlowStatusException=状态异常\nAccountInvalidateException=账户不合法\nAmountInvalidateException=金额输入错误\nStatusNotValidateException=状态错误\nFlowMaxCountException=24小时内最多添加400条账单记录\n\n#Transfer\nTransferFromEqualsToException=操作失败，转入和转出的账户不能相同。\n\n#Category\nCategoryHasDealException=类别下面有账单记录，无法删除。\nCategoryNameExistsException=类别名称已存在\nParentCategoryNotEnableException=父级类别不可用\nCategoryIsDefaultExpenseException=此分类为默认支出分类，无法操作。\nCategoryIsDefaultIncomeException=此分类为默认收入分类，无法操作。\nCategoryMaxCountException=最多添加200个分类\n\n# Payee\nPayeeHasDealException=此交易对象存在交易记录，无法删除。\nPayeeMaxCountException=最多添加300个交易对象\n\n# Tag\nTagHasTransactionException=此标签存在账单记录，无法删除。\nTagMaxCountException=最多添加300个标签\n\n#User\nRegisterNameExistsException=注册失败，用户名已存在。\nSigninFailedException=登录失败，用户名或密码错误。\nInviteCodeErrorException=邀请码错误\nUploadNotImageException=上传的文件类型不支持\nOldPasswordErrorException=原始密码错误\nUserDisabledException=用户已禁用\n\n#Report\nBreakOutOfMaxException=区间划分超过50\n\n#group\nGroupHasBookException=该组下存在账本，无法操作。\nGroupMaxCountException=最多添加5个分组\n\n#Book\nBookMaxCountException=最多添加200个账本\n\n#DealImage\nImageExistsException=图片文件已存在，不能重复上传。\nUploadKeyEmptyException=请配置上传需要的环境变量\n\nTokenEmptyException=请先登录\nTokenNotValidException=登录状态已过期，请重新登录。\nPermissionException=PermissionException\n\n#Item\nItemCountException=执行已到最大\n\n# common message\ncategoryOther = 其他\nexpense = 支出\nincome = 收入\nremain = 结余\n\nasset = 资产\ndebt = 负债\nnetWorth = 净资产"
  },
  {
    "path": "bookkeeping-user-api/start.sh",
    "content": "#!/bin/bash\nexport $(grep -v '^#' .env | xargs)\n./mvnw spring-boot:run\n"
  },
  {
    "path": "bookkeeping-user-api/startup.sh",
    "content": "#!/bin/bash\nexport $(grep -v '^#' .env | xargs)\nnohup java -jar bookkeeping-user-0.1.jar> 1.log 2>&1 &\necho $! > pid.file"
  },
  {
    "path": "bookkeeping-user-api/stop.sh",
    "content": "#!/bin/bash\nkill $(cat pid.file)\n\n#ps -ef | grep 'bookkeeping-user-0.1.jar' | grep -v grep | awk '{print $2}' | xargs kill -9\n#ps -ef|grep -v grep|grep bookkeeping-user-0.1.jar | grep java |awk '{print \"kill -9 \"$2}'|sh\n"
  },
  {
    "path": "bookkeeping-user-fe/.editorconfig",
    "content": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.md]\ntrim_trailing_whitespace = false\n\n[Makefile]\nindent_style = tab\n"
  },
  {
    "path": "bookkeeping-user-fe/.eslintrc",
    "content": "{\n  \"extends\": \"eslint-config-umi\"\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/.prettierignore",
    "content": "**/*.md\n**/*.svg\n**/*.ejs\n**/*.html\npackage.json\n.umi\n.umi-production\n"
  },
  {
    "path": "bookkeeping-user-fe/.prettierrc",
    "content": "{\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"printWidth\": 100,\n  \"overrides\": [\n    {\n      \"files\": \".prettierrc\",\n      \"options\": { \"parser\": \"json\" }\n    }\n  ]\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/.umirc.js",
    "content": "// ref: https://umijs.org/config/\nimport { defineConfig } from 'umi';\n\nexport default defineConfig({\n  proxy: {\n    '/api/v1': {\n      'target': 'http://127.0.0.1:9092/',\n      // 'target': 'http://testjz.jiukuaitech.com/',\n      'changeOrigin': true,\n    },\n  },\n  // publicPath: process.env.NODE_ENV === 'production' ? 'http://static.jiukuaitech.com/' : '/',\n  // hash: true,\n  antd: {\n    compact: true\n  },\n  dva: {\n    hmr: true,\n  },\n  locale: {\n    default: 'zh-CN',\n  },\n  // dynamicImport: {\n  //\n  // },\n  title: '九快记账',\n})\n\n\n"
  },
  {
    "path": "bookkeeping-user-fe/Dockerfile",
    "content": "#FROM nginx:1.21-alpine\n#COPY ./docker/nginx.conf.template /etc/nginx/templates/default.conf.template\n#COPY ./docker/gzip.conf /etc/nginx/conf.d/gzip.conf\n#COPY ./dist/ /usr/share/nginx/html\n\nFROM node:16-slim as build\nWORKDIR /workspace/app\nCOPY src src\nCOPY package.json .\nCOPY package-lock.json .\nRUN npm install\nRUN npm run build\n\nFROM nginx:1.21-alpine\nCOPY ./docker/nginx.conf.template /etc/nginx/templates/default.conf.template\nCOPY ./docker/gzip.conf /etc/nginx/conf.d/gzip.conf\nCOPY --from=build /workspace/app/dist/ /usr/share/nginx/html\n"
  },
  {
    "path": "bookkeeping-user-fe/docker/gzip.conf",
    "content": "gzip on;\ngzip_min_length  1k;\ngzip_buffers     4 16k;\ngzip_http_version 1.1;\ngzip_comp_level 2;\ngzip_types     text/plain application/javascript application/x-javascript text/javascript text/css application/xml;\ngzip_vary on;\ngzip_proxied   expired no-cache no-store private auth;\n"
  },
  {
    "path": "bookkeeping-user-fe/docker/nginx.conf",
    "content": "server {\n    listen       80;\n    listen  [::]:80;\n    server_name  localhost;\n    location / {\n        root   /usr/share/nginx/html;\n        index  index.html index.htm;\n        try_files $uri $uri/ /index.html;\n    }\n    location ~ ^/api/v1 {\n        proxy_pass   http://user-api:9092;\n    }\n}"
  },
  {
    "path": "bookkeeping-user-fe/docker/nginx.conf.template",
    "content": "server {\n    listen       80;\n    listen  [::]:80;\n    server_name  localhost;\n    location / {\n        root   /usr/share/nginx/html;\n        index  index.html index.htm;\n        try_files $uri $uri/ /index.html;\n    }\n    location ~ ^/api/v1 {\n        proxy_pass   ${API_HOST};\n        proxy_set_header Host $http_host;\n        proxy_set_header Cookie $http_cookie;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/mock/.gitkeep",
    "content": ""
  },
  {
    "path": "bookkeeping-user-fe/package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"start\": \"umi dev\",\n    \"build\": \"umi build\"\n  },\n  \"dependencies\": {\n    \"@antv/data-set\": \"^0.11.8\",\n    \"bizcharts\": \"^3.5.3-beta.0\",\n    \"dva-model-extend\": \"^0.1.2\"\n  },\n  \"devDependencies\": {\n    \"cross-env\": \"^5.0.5\",\n    \"umi\": \"^3.5.0\",\n    \"@umijs/preset-react\": \"^1.4.8\"\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/app.js",
    "content": "export const dva = {\n  config: {\n    onError(err) {\n      err.preventDefault();\n      console.error(err.message);\n    },\n  },\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AccountRecordTable/index.jsx",
    "content": "import { useEffect, useState } from \"react\";\nimport { useResultPaginationAndData } from '@/utils/hooks';\nimport { tableChangeQueryFormat } from \"@/utils/util\";\nimport {query as queryFlows} from '@/services/flow'\nimport FlowRecordTable from '@/components/FlowRecordTable';\n\nexport default (props) => {\n\n  // currentTime刷新使用\n  const { accountId, currentTime } = props;\n\n  const [queryResponse, setQueryResponse] = useState();\n  const [ dataAndPagination ] = useResultPaginationAndData(queryResponse);\n  const [loading, setLoading] = useState(false);\n  async function fetchData(id, query) {\n    setLoading(true);\n    let response = await queryFlows(Object.assign({accountId: id}, query));\n    setQueryResponse(response);\n    setLoading(false);\n  }\n\n  function handleTableChange(pagination, _, sorter) {\n    fetchData(accountId, tableChangeQueryFormat(pagination, sorter));\n  }\n\n  useEffect(() => {\n    fetchData(accountId, dataAndPagination.pagination.current, dataAndPagination.pagination.pageSize);\n  }, [accountId, currentTime]);\n\n  return (\n    <FlowRecordTable\n      bordered={false}\n      data={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      queryLoading={loading}\n      tableChangeHandler={handleTableChange}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AdjustBalanceModal/index.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useSelector } from 'umi';\nimport { Form, Space, Input } from 'antd';\nimport FormModal from \"@/components/FormModal\";\nimport FormItemDescription from '@/components/FormItemDescription';\nimport FormItemCreateTime from '@/components/FormItemCreateTime';\nimport moment from 'moment';\nimport {balanceRequiredRules, notesRules} from '@/utils/rules';\nimport { adjustBalance } from '@/services/account';\nimport {formProp} from \"@/utils/var\";\nimport {getNull, refreshFlow} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\nimport styles from \"./index.less\";\n\nexport default () => {\n\n  const [form] = Form.useForm();\n  const { visible, currentItem } = useSelector(state => state.modal);\n\n  const [initialValues, setInitialValues] = useState({});\n  useEffect(() => {\n    if (visible) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()), //统一将简单属性置空\n        'createTime': moment(),\n      });\n    }\n  }, [visible]);\n\n  function successHandler(response) {\n    refreshFlow(response.data);\n  }\n\n  return (\n    <FormModal\n      title={t('adjust.balance') + ' - ' + currentItem.name}\n      form={form}\n      initialValues={initialValues}\n      update={adjustBalance}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form {...formProp} form={form} className={styles['form']}>\n        <FormItemDescription />\n        <Form.Item label={t('account.balance')}>\n          <Space>\n            <Form.Item name=\"balance\" rules={balanceRequiredRules()} noStyle><Input /></Form.Item>\n            <div>{t('account.current.balance')}: <span style={{color:\"#14ba89\"}}>{currentItem.balance}</span></div>\n          </Space>\n        </Form.Item>\n        <FormItemCreateTime />\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AdjustBalanceModal/index.less",
    "content": ".form {\n  :global {\n    .ant-form-item-label {\n      width: 70px;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AlertTotalSearch/index.jsx",
    "content": "import { Alert } from 'antd';\n\nexport default (props) => {\n\n  const { message } = props;\n\n  return (\n    <Alert style={{display:'inline-flex', paddingTop:0, paddingBottom:0}} message={message} type=\"info\" showIcon />\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AmountInput/index.jsx",
    "content": "import { Input } from 'antd';\n\nexport default (props) => {\n\n  const { amount: value, positive = 0, onBlur:blurHandler, onChange: changeHandler} = props;\n\n  const onChange = e => {\n    const { value } = e.target;\n    const reg = /^-?\\d{1,9}(\\.\\d{0,2})?$/;\n    if (positive && value === '-') {\n      changeHandler('');\n      return false;\n    }\n    if ((!isNaN(value) && reg.test(value)) || value === '' || value === '-') {\n      changeHandler(value);\n    }\n\n  };\n\n  // '.' at the end or only '-' in the input box.\n  const onBlur = e => {\n    if (value) {\n      let valueTemp = value;\n      if (value.charAt(value.length - 1) === '.' || value === '-') {\n        valueTemp = value.slice(0, -1);\n      }\n      changeHandler(valueTemp.replace(/0*(\\d+)/, '$1'));\n    }\n    if (blurHandler) {\n      blurHandler();\n    }\n  };\n\n\n  return (\n      <Input\n        {...props}\n        value={value}\n        onChange={onChange}\n        onBlur={onBlur}\n        placeholder=\"请输入数字\"\n        maxLength={12}\n      />\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AmountInputPositive/index.jsx",
    "content": "import { Input } from 'antd';\n\nexport default (props) => {\n\n  const onChange = e => {\n    const { value } = e.target;\n    const reg = /^-?\\d{1,9}(\\.\\d{0,2})?$/;\n    if (value !== '-') {\n      if ((!isNaN(value) && reg.test(value)) || value === '' || value === '-') {\n        props.onChange(value);\n      }\n    }\n  };\n\n  // '.' at the end or only '-' in the input box.\n  const onBlur = () => {\n    const { value1:value, onBlur, onChange } = props;\n    let valueTemp = value;\n    if (value.charAt(value.length - 1) === '.' || value === '-') {\n      valueTemp = value.slice(0, -1);\n    }\n    onChange(valueTemp.replace(/0*(\\d+)/, '$1'));\n    if (onBlur) {\n      onBlur();\n    }\n  };\n\n\n  return (\n    <Input\n      {...props}\n      value={props.value1}\n      onChange={onChange}\n      onBlur={onBlur}\n      placeholder=\"请输入数字\"\n      maxLength={12}\n    />\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AvatarDropdown/UpdatePasswordModal.jsx",
    "content": "import {Form, Input} from 'antd';\nimport { updatePassword } from '@/services/user';\nimport {requiredRules} from \"@/utils/rules\";\nimport FormModal from \"@/components/FormModal\";\nimport styles from './index.less';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const [form] = Form.useForm();\n\n  function successHandler() {\n    window.location.href = '/signin';\n  }\n\n  return (\n    <FormModal\n      title={t('update.password')}\n      form={form}\n      create={updatePassword}\n      onSuccess={successHandler}\n    >\n      <Form form={form} className={styles['form3']}>\n        <Form.Item label={t('old.password')} name=\"oldPassword\" rules={requiredRules()}>\n          <Input type=\"password\" />\n        </Form.Item>\n        <Form.Item label={t('new.password')} name=\"newPassword\" rules={requiredRules()}>\n          <Input type=\"password\" />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AvatarDropdown/index.jsx",
    "content": "import {useEffect} from \"react\";\nimport {useSelector, useDispatch} from 'umi';\nimport {Avatar, Menu, message, Spin} from 'antd';\nimport moment from \"moment\";\nimport { signout } from '@/services/user';\nimport { LogoutOutlined, LockOutlined, UserOutlined } from '@ant-design/icons';\nimport HeaderDropdown from '@/components/HeaderDropdown';\nimport UpdatePasswordModal from './UpdatePasswordModal';\nimport t from '@/utils/translate';\nimport styles from './index.less';\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n  const { user } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!user) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n  const messageOperationSuccess = t('operation.success');\n  const onMenuClick = async (event) => {\n    const { key } = event;\n    if (key === 'logout') {\n      const response = await signout();\n      if (response && response.success) {\n        message.success(messageOperationSuccess);\n        window.location.href = \"/signin\";\n      }\n    }\n    if (key === 'updatePassword') {\n      dispatch({ type: 'modal/show', payload: {component: UpdatePasswordModal }});\n    }\n  };\n\n  const {\n    currentUser = {\n      avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',\n      name: user && user.userName ? user.userName : t('signin'),\n      vipTime:  user && user.vipTime ? user.vipTime : 0\n    },\n    menu,\n  } = props;\n\n  const menuHeaderDropdown = (\n    <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>\n      {menu && (<Menu.Item key=\"center\"><UserOutlined />{t('profile')}</Menu.Item>)}\n      {menu && (<Menu.Item key=\"updatePassword\"><LockOutlined />{t('update.password')}</Menu.Item>)}\n      {/*{menu && (<Menu.Item key=\"settings\"><SettingOutlined />{t('settings')}</Menu.Item>)}*/}\n      {menu && <Menu.Divider />}\n      {menu && (<Menu.Item key=\"logout\"><LogoutOutlined />{t('logout')}</Menu.Item>)}\n      {menu && (<Menu.Item><span>会员到期时间: {moment(currentUser.vipTime).format('YYYY-MM-DD')}</span></Menu.Item>)}\n    </Menu>\n  );\n\n  return currentUser && currentUser.name ? (\n    <HeaderDropdown overlay={menuHeaderDropdown}>\n        <span className={styles['avatar']}>\n          <Avatar size=\"small\" src={currentUser.avatar} alt=\"avatar\" style={{marginRight: 10}} />\n          <span style={{color: \"rgba(0,0,0,0.85\"}}>{currentUser.name}</span>\n        </span>\n    </HeaderDropdown>\n  ) : (\n    <span>\n        <Spin\n          size=\"small\"\n          style={{\n            marginLeft: 8,\n            marginRight: 8,\n          }}\n        />\n      </span>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/AvatarDropdown/index.less",
    "content": ".avatar {\n  display: inline-block;\n  height: 100%;\n  padding: 0 20px;\n  cursor: pointer;\n  transition: color 0.3s;\n  &:hover {\n    background: rgba(0, 0, 0, 0.025);\n  }\n}\n\n.form3 {\n  :global {\n    .ant-form-item-label {\n      width: 75px;\n    }\n  }\n}\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/Breadcrumb/index.jsx",
    "content": "import { Breadcrumb } from 'antd';\n\nexport default (props) => {\n  return (\n    <Breadcrumb>\n      { props.data.map((item, index) => <Breadcrumb.Item key={index}>{item}</Breadcrumb.Item>) }\n    </Breadcrumb>\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/ExpenseModal/index.jsx",
    "content": "import {useEffect, useState} from 'react';\nimport {useDispatch, useSelector} from 'umi';\nimport {Form, Input, Switch} from 'antd';\nimport moment from 'moment';\nimport {\n  useCategoryTreeSelectData,\n  useResponseData,\n  useResponseSelectData\n} from \"@/utils/hooks\";\nimport {getNull, refreshFlow} from \"@/utils/util\";\nimport FormModal from \"@/components/FormModal\";\nimport FormItemAccount from '@/components/FormItemAccount';\nimport FormListCategory from '@/components/FormListCategory';\nimport FormItemTag from '@/components/FormItemTag';\nimport FormItemPayee from '@/components/FormItemPayee';\nimport FormItemDescription from '@/components/FormItemDescription';\nimport FormItemCreateTime from '@/components/FormItemCreateTime';\nimport { create, update, refund } from '@/services/expense';\nimport t from '@/utils/translate';\nimport styles from './index.less';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { defaultBook } = useSelector(state => state.session);\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const { getExpenseableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getExpenseableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees, setPayees] = useResponseSelectData(payeeResponse);\n\n  const { getExpenseableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseData(accountResponse);\n\n  const { querySimpleResponse } = useSelector(state => state.expenseCategory);\n  const [categories] = useCategoryTreeSelectData(querySimpleResponse);\n\n  // TODO 很多地方有这个只加载一次的数据，待优化\n  useEffect(() => {\n    if (visible) {\n      if (!defaultBook) dispatch({ type: 'session/fetchSession' });\n      if (!tagResponse) dispatch({ type: 'tag/fetchExpenseable' });\n      if (!payeeResponse) dispatch({ type: 'payee/fetchExpenseable' });\n      if (!accountResponse) dispatch({ type: 'account/fetchExpenseable' });\n      if (!querySimpleResponse) dispatch({ type: 'expenseCategory/fetchSimple' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({}); // 默认值必须为空，currentItem报错date.clone is not a function，调试了半天。\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()), //清空上次输入的\n        'createTime': moment(),\n        'accountId': defaultBook && defaultBook.defaultExpenseAccount && defaultBook.defaultExpenseAccount.id,\n        'categories': defaultBook && defaultBook.defaultExpenseCategory ? [{ categoryId: defaultBook.defaultExpenseCategory.id }] : [{ }],\n        'tags': [],\n        'confirmed': true,\n      });\n      if (defaultBook && defaultBook.defaultExpenseAccount) {\n        setAccountCurrencyCode(defaultBook.defaultExpenseAccount.currencyCode);\n      } else {\n        setAccountCurrencyCode('');\n      }\n    } else {\n      let currentItemCopy = JSON.parse(JSON.stringify(currentItem));\n      if (type === 4) {\n        if (currentItemCopy.categories) currentItemCopy.categories.forEach(value => {\n          value.amount = value.amount*(-1);\n          value.convertedAmount = value.convertedAmount*(-1)\n        });\n      }\n      if (type === 3 || type === 4) { //不复制备注\n        currentItemCopy.notes = \"\";\n      }\n      // 数字类型的校验存在问题, antd bug\n      if (currentItemCopy.categories) currentItemCopy.categories.forEach(value => {value.amount = value.amount.toString();value.convertedAmount = value.convertedAmount.toString()});\n      currentItemCopy.createTime = type === 2 ? moment(currentItemCopy.createTime) : moment();\n      currentItemCopy.accountId = currentItemCopy.account ? currentItemCopy.account.id : null;\n      currentItemCopy.payeeId = currentItemCopy.payee ? currentItemCopy.payee.id : null;\n      currentItemCopy.tags = currentItemCopy.tags ? currentItemCopy.tags.map(item => item.tagId) : null;\n      currentItemCopy.confirmed = type === 3 || type === 4 ? true : currentItemCopy.status === 1;\n      /*\n      处理修改时，账户已禁用的情况，未完。\n      if (type === 3) {\n        if (!currentItemCopy.account.enable) {\n          currentItemCopy.accountId = null;\n        }\n      } else if (!currentItemCopy.account.enable) {\n        if (!accounts.some(e => e.id === currentItemCopy.account.id)) {\n          setAccounts([{ id: currentItemCopy.account.id, name: currentItemCopy.account.name }, ...accounts]);\n        }\n      }\n      */\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        ...currentItemCopy,\n        'categories': currentItemCopy.categories ? currentItemCopy.categories : [{}],\n      });\n      setAccountCurrencyCode(currentItemCopy.currencyCode);\n    }\n  }, [visible, type, currentItem]);\n\n  function successHandler(response) {\n    refreshFlow(response.data);\n  }\n\n  const [accountCurrencyCode, setAccountCurrencyCode] = useState();\n  const accountChangeHandler = (value) => {\n    for (let i = 0; i < accounts.length; i++) {\n      if (accounts[i].id === value) {\n        setAccountCurrencyCode(accounts[i].currencyCode);\n      }\n    }\n  }\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : type === 2 ? t('update') : type === 3 ? t('copy') : t('refund')) + t('expense')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      refund={refund}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form form={form} className={styles['form']}>\n        <FormItemDescription />\n        <FormItemCreateTime />\n        <FormItemAccount data={accounts} required={true} onSelectChange={accountChangeHandler} />\n        <FormListCategory categories={categories} isConvert={defaultBook && accountCurrencyCode && defaultBook.defaultCurrencyCode !== accountCurrencyCode} currency={t('convertCurrency')+defaultBook.defaultCurrencyCode} />\n        <FormItemPayee form={form} payees={payees} setPayees={setPayees} type={1} />\n        <FormItemTag data={tags} type={1} />\n        {(type !== 2) &&\n        <Form.Item label={t('flow.isConfirmed')} valuePropName=\"checked\" name=\"confirmed\">\n          <Switch />\n        </Form.Item>\n        }\n        <Form.Item label={t('notes')} name=\"notes\">\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/ExpenseModal/index.less",
    "content": ".form {\n  :global {\n    .ant-form-item-label {\n      width: 70px;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FlagTag/index.jsx",
    "content": "import { Tag } from \"antd\";\nimport { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons';\nimport t from \"@/utils/translate\";\n\nexport default (props) => {\n\n  return (\n    props.value ?\n      <Tag icon={<CheckCircleOutlined />} color=\"success\" style={{marginRight: 0}}>{t('yes')}</Tag> :\n      <Tag icon={<CloseCircleOutlined />} color=\"error\" style={{marginRight: 0}}>{t('no')}</Tag>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FlowButtons/index.jsx",
    "content": "import {Button, Space} from \"antd\";\nimport { PlusOutlined } from '@ant-design/icons';\nimport {showFlowModal} from '@/utils/flow';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  return (\n    <Space size=\"large\">\n      <Button type=\"primary\" size=\"large\" icon={<PlusOutlined />} onClick={()=>showFlowModal(1, 1, {})}>{t('new') + t('expense')}</Button>\n      <Button type=\"primary\" size=\"large\" icon={<PlusOutlined />} onClick={()=>showFlowModal(2, 1, {})}>{t('new') + t('income')}</Button>\n      <Button type=\"primary\" size=\"large\" icon={<PlusOutlined />} onClick={()=>showFlowModal(3, 1, {})}>{t('new') + t('transfer')}</Button>\n    </Space>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FlowImageUploadModal/index.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport {message, Modal, Upload} from \"antd\";\nimport {updateImages} from '@/services/flow';\nimport t from \"@/utils/translate\";\nimport {PlusOutlined} from \"@ant-design/icons\";\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n\n  const { user } = useSelector(state => state.session);\n  const { visible, currentItem } = useSelector(state => state.modal);\n  const { images, uploadToken } = useSelector(state => state.flow);\n\n  useEffect(() => {\n    if (currentItem && currentItem.id) {\n      dispatch({ type: 'flow/fetchImages', payload: {id: currentItem.id} });\n    }\n  }, [currentItem]);\n\n  useEffect(() => {\n    if (visible) {\n      dispatch({ type: 'flow/fetchUploadToken' })\n    }\n  }, [visible]);\n\n  function cancelHandler() {\n    dispatch({ type: 'modal/hide' });\n  }\n\n  useEffect(() => {\n    if (images) {\n      setFileList(\n        images.map(image => {\n          return {\n            uid: image.id,\n            id: image.id,\n            status: 'done',\n            url: image.url,\n          }\n        })\n      );\n    }\n  }, [images]);\n\n  const [fileList, setFileList] = useState([]);\n  const [uploadError, setUploadError] = useState(false);\n  useEffect(() => {\n    if (uploadError) {\n      let newFileList = [...fileList];\n      setFileList(newFileList.filter(file => {\n        if (!file.status) return false; //beforeUpload没通过\n        if (file.status === 'error') return false; //七牛报错\n        if (file.status === 'done' && (file.response && !file.response.success)) return false; //文件已存在，回调报错\n        return true;\n      }));\n      setUploadError(false);\n    }\n  }, [uploadError]);\n\n  const messageFileSize = t('upload.size.error');\n  const uploadProps = {\n    accept: 'image/jpeg, image/png, application/pdf',\n    multiple: true,\n    maxCount: 3,\n    fileList: fileList,\n    listType: 'picture-card',\n    action: 'http://upload-z2.qiniup.com',\n    data: {\n      'token': uploadToken,\n      'x:userId': user && user.id\n    },\n    onChange(info) {\n      let newFileList = [...info.fileList];\n      newFileList = newFileList.map(file => {\n        if (file.status === 'done' && file.response && file.response.success) {\n          file.url = file.response.data.url;\n          file.id = file.response.data.id;\n        }\n        return file;\n      });\n      setFileList(newFileList);\n      if (info.file.status === 'error') {\n        message.error(info.file.response.error);\n        setUploadError(true);\n      }\n      if (info.file.status === 'done') {\n        if (!info.file.response.success) {\n          message.error(info.file.response.errorMsg);\n          setUploadError(true);\n        }\n      }\n    },\n    beforeUpload(file) {\n      let isOk = true;\n      if (file.size > 1.5*1024*1024) {\n        message.error(messageFileSize);\n        isOk = false;\n      }\n      if (isOk) dispatch({ type: 'flow/fetchUploadToken' });\n      if (!isOk) setUploadError(true);\n      return isOk;\n    }\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const [confirmLoading, setConfirmLoading] = useState(false);\n  async function okHandler() {\n    setConfirmLoading(true);\n    const response = await updateImages(currentItem.id, fileList.map(i=>i.id));\n    if (response && response.success) {\n      cancelHandler();\n      message.success(messageOperationSuccess);\n    }\n    setConfirmLoading(false);\n  }\n\n  return (\n    <Modal\n      forceRender={true}\n      maskClosable={false}\n      title={t('flow.image')}\n      visible={visible}\n      onOk={okHandler}\n      onCancel={cancelHandler}\n      confirmLoading={confirmLoading}\n    >\n      <Upload {...uploadProps}>\n        {\n          fileList.length >= 3 ?\n            null :\n            <div>\n              <PlusOutlined />\n              <div style={{ marginTop: 8 }}>Upload</div>\n            </div>\n        }\n      </Upload>\n\n    </Modal>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FlowRecordTable/index.jsx",
    "content": "import {Button, message, Modal, Space, Table, Descriptions, Dropdown, Menu} from 'antd';\nimport {DownOutlined} from \"@ant-design/icons\";\nimport {tableProp} from \"@/utils/var\";\nimport {amountCol, createTimeCol, flowTypeCol, statusCol} from '@/utils/columns';\nimport {useDescriptionEnable, useImageEnable, useTimeFormat} from \"@/utils/hooks\";\nimport {refreshFlow} from \"@/utils/util\";\nimport {imageHandler, showFlowModal} from '@/utils/flow';\nimport FlowTagDisplay from '@/components/FlowTagDisplay';\nimport { remove, confirm } from '@/services/flow';\nimport t from \"@/utils/translate\";\n\nexport default (props) => {\n\n  const {\n    queryLoading,\n    data,\n    pagination,\n    tableChangeHandler,\n    bordered,\n    noBook,\n  } = props;\n\n  const imageEnable = useImageEnable();\n  const descriptionEnable = useDescriptionEnable();\n  const timeFormat = useTimeFormat();\n\n  const messageDeleteConfirm = t('delete.confirm', { name: '' });\n  const messageDeleteConfirmBalance = t('delete.confirm.balance');\n  const messageOperationSuccess = t('operation.success');\n  function deleteHandler(record) {\n    Modal.confirm({\n      title: record.status === 2 ? messageDeleteConfirm : messageDeleteConfirmBalance,\n      onOk: async () => {\n        const response = await remove(record);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refreshFlow(response.data);\n        }\n      }\n    });\n  }\n\n  const messageConfirmOperation = t('confirm.operation');\n  function confirmHandler(record) {\n    Modal.confirm({\n      title: messageConfirmOperation,\n      onOk: async () => {\n        const response = await confirm(record);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refreshFlow(response.data);\n        }\n      }\n    });\n  }\n\n  function refundHandler(record) {\n    if (record.type === 1) {\n      showFlowModal(1, 4, { ...record.expense });\n    } else if (record.type === 2) {\n      showFlowModal(2, 4, { ...record.income });\n    }\n  }\n\n  function copyHandler(record) {\n    if (record.type === 1) {\n      showFlowModal(1, 3, { ...record.expense });\n    } else if (record.type === 2) {\n      showFlowModal(2, 3, { ...record.income });\n    } else if (record.type === 3) {\n      showFlowModal(3, 3, { ...record.transfer });\n    }\n  }\n\n  function updateHandler(record) {\n    if (record.type === 1) {\n      showFlowModal(1, 2, { ...record.expense });\n    } else if (record.type === 2) {\n      showFlowModal(2, 2, { ...record.income });\n    } else if (record.type === 3) {\n      showFlowModal(3, 2, { ...record.transfer });\n    }\n  }\n\n  let columns = [];\n  if (!noBook) {\n    columns.push({\n      title: t('flow.book'),\n      dataIndex: 'bookName',\n    });\n  }\n  if (descriptionEnable) {\n    columns.push({\n      title: t('description'),\n      dataIndex: 'description',\n    });\n  }\n  columns = columns.concat(\n    [\n      flowTypeCol(),\n      amountCol(),\n      createTimeCol(timeFormat),\n      {\n        title: t('account'),\n        dataIndex: 'accountName',\n      },\n      {\n        title: t('category'),\n        dataIndex: 'categoryName',\n      },\n      {\n        title: t('flow.tag'),\n        dataIndex: 'tags',\n        render: (tags, record) => <>{tags.map(i => <FlowTagDisplay record={record} data={i} key={i.tagId} />)}</>\n      },\n      {\n        title: t('flow.payee'),\n        dataIndex: 'payee',\n        sorter: true,\n        render: payee => <>{payee ? payee.name : null}</>\n      },\n      statusCol(),\n      {\n        title: t('operation'),\n        key: 'operation',\n        width: 100,\n        align: 'center',\n        render: (_, record) => {\n          return (\n            <Space size=\"small\">\n              <Button type=\"link\" size=\"small\" disabled={record.type === 4} onClick={() => copyHandler(record)}>{t('copy')}</Button>\n              <Dropdown overlay={\n                <Menu>\n                  {imageEnable ? <Menu.Item onClick={() => imageHandler(record)}>{t('image')}</Menu.Item> : null}\n                  <Menu.Item disabled={record.type === 4} onClick={() => updateHandler(record)}>{t('update')}</Menu.Item>\n                  <Menu.Item disabled={record.status !== 2} onClick={() => confirmHandler(record)}>{t('confirm')}</Menu.Item>\n                  <Menu.Item disabled={!((record.type === 1 || record.type === 2) && (record.status !== 2 && record.amount > 0))} onClick={() => refundHandler(record)}>{t('refund')}</Menu.Item>\n                  <Menu.Item disabled={record.status == 3} onClick={() => deleteHandler(record)}>{t('delete')}</Menu.Item>\n                </Menu>\n              }>\n                <a>\n                  {t('more')} <DownOutlined />\n                </a>\n              </Dropdown>\n            </Space>\n          )\n        }\n      }\n    ]\n  );\n\n  function rowExpandableRecord(record) {\n    if (record.notes) {\n      return true;\n    }\n    return record.needConvert;\n  }\n\n  function descriptionsItemRecord(record) {\n    let notesItem = null;\n    if (record.notes) {\n      notesItem = <Descriptions.Item label={t('notes')}>{record.notes}</Descriptions.Item>;\n    }\n    let currencyItem = null;\n    if (record.needConvert) {\n      currencyItem = <Descriptions.Item label={t('convertCurrency')+record.toCurrencyCode}>{record.convertedAmount}</Descriptions.Item>\n    }\n    return <>{notesItem}{currencyItem}</>;\n  }\n\n  return (\n    <Table\n      {...tableProp}\n      bordered={bordered}\n      columns={columns}\n      expandable={{\n        expandedRowRender: record => <Descriptions size=\"small\" bordered>\n          {descriptionsItemRecord(record)}\n        </Descriptions>,\n        rowExpandable: record => rowExpandableRecord(record),\n      }}\n      dataSource={data}\n      pagination={pagination}\n      loading={queryLoading}\n      onChange={tableChangeHandler}\n    />\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FlowStatusDisplay/index.jsx",
    "content": "import {useMemo} from \"react\";\nimport {Tag, Tooltip} from \"antd\";\nimport { history } from 'umi';\nimport {flowStatusToColor} from \"@/utils/util\";\nimport {getRefunds} from '@/services/deal';\n\n\nexport default (props) => {\n\n  const { data } = props;\n\n  const clickable = useMemo(() => data.status === 3 || data.amount < 0, [data]);\n\n  async function clickHandler() {\n    const response = await getRefunds(data.id);\n    if (response && response.success) {\n      history.push({\n        pathname: history.pathname,\n        query: {\n          id: response.data.join(','),\n        },\n      });\n    }\n  }\n\n  // TODO 多语言\n  return (\n    clickable ?\n    <Tooltip title=\"点击查看对应退款记录\">\n      <Tag style={{cursor:\"pointer\", marginRight:0}} onClick={clickHandler} color={flowStatusToColor(data.status)}>{data.statusName}</Tag>\n    </Tooltip>\n      :\n    <Tag style={{marginRight:0}} color={flowStatusToColor(data.status)}>{data.statusName}</Tag>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FlowTagDisplay/TagModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport {history, useDispatch, useSelector} from 'umi';\nimport {Form, Input, message} from 'antd';\nimport {getNull, validateForm, refreshFlow} from \"@/utils/util\";\nimport {amountRequiredRules} from \"@/utils/rules\";\nimport FormModal from \"@/components/FormModal\";\nimport {update} from '@/services/tag-relation';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, currentItem } = useSelector(state => state.modal);\n  const { defaultBook } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultBook) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n  const [initialValues, setInitialValues] = useState({})\n  useEffect(() => {\n    if (visible) {\n      currentItem.amount = currentItem.amount.toString();\n      setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n    }\n  }, [visible]);\n\n  function successHandler() {\n    refreshFlow();\n  }\n\n  return (\n    <FormModal\n      title={t('update.tag.amount')}\n      form={form}\n      initialValues={initialValues}\n      update={update}\n      onSuccess={successHandler}\n    >\n      <Form form={form}>\n        <Form.Item label={t('tag.amount')} name=\"amount\" rules={amountRequiredRules()}>\n          <Input />\n        </Form.Item>\n        {\n          defaultBook && defaultBook.defaultCurrencyCode !== currentItem.currencyCode ?\n          <Form.Item label={t('convertCurrency')+defaultBook.defaultCurrencyCode} name=\"convertedAmount\" rules={amountRequiredRules()}>\n            <Input />\n          </Form.Item> : null\n        }\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FlowTagDisplay/index.jsx",
    "content": "import {useState} from \"react\";\nimport {Tag, Tooltip} from \"antd\";\nimport {useDispatch} from \"umi\";\nimport TagModal from './TagModal';\nimport t from \"@/utils/translate\";\n\nexport default (props) => {\n\n  const { data, record } = props;\n  const dispatch = useDispatch();\n\n  const updateTagAmountHandler = () => {\n    dispatch({ type: 'modal/show', payload: {component: TagModal, type: 2, currentItem: {currencyCode: record.currencyCode, ...data} } });\n  }\n\n  // TODO 多语言\n  return (\n    record.categoryName || record.type === 1 || record.type === 2 ?\n    <Tooltip title={`金额: ${data.amount}, 点击修改`}><Tag onClick={updateTagAmountHandler} style={{cursor:\"pointer\"}} color=\"blue\">{data.tagName}</Tag></Tooltip>\n      :\n    <Tag color=\"blue\">{data.tagName}</Tag>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/Footer/index.jsx",
    "content": "import { Space } from 'antd';\nimport t from '@/utils/translate';\n\nimport styles from './index.less';\n\nexport default () => {\n  return (\n    <Space className={styles['footer']} direction=\"vertical\" size=\"small\" style={{ width:\"100%\" }}>\n      <div className={styles['footer-link']}>\n        <Space size=\"small\">\n          <a href=\"#\">{t('footer.download.ios')}</a>\n          <a href=\"#\">{t('footer.download.andriod')}</a>\n          <a href=\"#\">{t('footer.download.weibo')}</a>\n          <a href=\"#\">{t('footer.download.zhihu')}</a>\n          <a href=\"#\">{t('footer.download.contact')}</a>\n          <a href=\"#\">{t('footer.download.about')}</a>\n        </Space>\n      </div>\n      <div>&copy; {new Date().getFullYear()} {t('company.name')}版权所有&nbsp;&nbsp;<a target=\"_blank\" href=\"https://beian.miit.gov.cn/\">{t('footer.no')}</a> v1.0.6</div>\n    </Space>\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/Footer/index.less",
    "content": ".footer {\n  text-align: center;\n}\n.footer-link {\n  a {\n    color: rgba(0, 0, 0, 0.45);\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemAccount/index.jsx",
    "content": "import {useMemo} from \"react\";\nimport {Form, Select} from 'antd';\nimport {accountRequiredRules} from \"@/utils/rules\";\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const { data, name = \"accountId\", label=t('account'), required=true, onSelectChange } = props;\n\n  // const messageBalance = t('account.balance');\n  const accounts = useMemo(() => {\n    if (data && data.length > 0) {\n      return data.map(i => {\n        return {\n          value: i.id,\n          //label: `${i.name} (${messageBalance}：${i.balance})`,\n          label: i.name\n        }\n      });\n    } else {\n      return 0;\n    }\n  }, [data]);\n\n  const changeHandler = (value) => {\n    if (onSelectChange) onSelectChange(value);\n  }\n\n  return (\n    <Form.Item label={label} name={name} rules={required ? accountRequiredRules() : null}>\n      <Select options={accounts} showArrow showSearch filterOption optionFilterProp=\"label\" allowClear={!required} onChange={changeHandler} />\n    </Form.Item>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemAccounts/index.jsx",
    "content": "import {Col, Form, Select} from 'antd';\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const { data, name = \"accounts\", label=t('account') } = props;\n\n  return (\n    <Col flex=\"auto\" style={{minWidth:300}}>\n      <Form.Item label={label} name={name}>\n        <Select mode=\"multiple\" options={data} allowClear showArrow showSearch filterOption optionFilterProp={\"label\"} />\n      </Form.Item>\n    </Col>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemAmountRange/index.jsx",
    "content": "import {useEffect} from \"react\";\nimport {Col, Form, Input, Radio, Space} from 'antd';\nimport t from '@/utils/translate';\n\nexport default () => {\n  return (\n    <Col flex=\"290px\">\n      <Form.Item label={t('amount.between')}>\n        <Space>\n          <span>\n            <Form.Item name=\"minAmount\" noStyle={true}>\n              <Input style={{ width: 90 }}/>\n            </Form.Item>\n            <span> ~ </span>\n            <Form.Item name=\"maxAmount\" noStyle={true}>\n              <Input style={{ width: 90 }}/>\n            </Form.Item>\n          </span>\n        </Space>\n      </Form.Item>\n    </Col>\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemCategories/index.jsx",
    "content": "import {Col, Form, TreeSelect} from 'antd';\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const { data, name = \"categories\", label = t('category') } = props;\n\n  return (\n    <Col flex=\"auto\" style={{minWidth:300}}>\n      <Form.Item label={label} name={name}>\n        <TreeSelect\n          treeDataSimpleMode={true}\n          treeData={data}\n          treeDefaultExpandAll={true}\n          showCheckedStrategy={TreeSelect.SHOW_ALL}\n          treeCheckStrictly={false}\n          showArrow={true}\n          showSearch={true}\n          allowClear={true}\n          treeNodeFilterProp=\"title\"\n          treeCheckable={true} />\n      </Form.Item>\n    </Col>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemCategoryReport/index.jsx",
    "content": "import {Col, Form, TreeSelect} from 'antd';\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const { data } = props;\n\n  return (\n    <Col flex=\"200px\">\n      <Form.Item label={t('category')} name=\"categoryId\">\n        <TreeSelect\n          treeDataSimpleMode={true}\n          treeData={data}\n          showArrow={true}\n          showSearch={true}\n          allowClear={true}\n          treeNodeFilterProp=\"title\"\n          treeDefaultExpandAll={false} />\n      </Form.Item>\n    </Col>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemCreateTime/index.jsx",
    "content": "import {DatePicker, Form} from 'antd';\nimport {timeRequiredRules} from \"@/utils/rules\";\nimport {getTimeEnable, getTimeFormat} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  return (\n    <Form.Item label={t('flow.createTime')} name=\"createTime\" rules={timeRequiredRules()}>\n      <DatePicker showTime={getTimeEnable()} format={getTimeFormat()} allowClear={false} />\n    </Form.Item>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemDateRange/index.jsx",
    "content": "import {Col, DatePicker, Form, Radio, Space} from 'antd';\nimport {useEffect} from \"react\";\nimport {radioValueToTimeRange,getTimeFormat} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\n\nexport default (props) => {\n\n  const { form, dateRadioValue, setDateRadioValue } = props;\n\n  function createTimeRadioChangeHandler(e) {\n    setDateRadioValue(e.target.value);\n  }\n  useEffect(() => {\n    form.setFieldsValue({\n      'createTimeRange': radioValueToTimeRange(dateRadioValue),\n    });\n  }, [dateRadioValue]);\n\n  return (\n    <Col flex=\"670px\">\n      <Form.Item label={t('time.between')}>\n        <Space>\n          <Form.Item name=\"createTimeRange\" noStyle={true}>\n            <DatePicker.RangePicker showTime={false} format='YYYY-MM-DD' />\n          </Form.Item>\n          <Radio.Group size=\"small\" onChange={createTimeRadioChangeHandler} value={dateRadioValue}>\n            <Radio.Button value={1}>{t('today')}</Radio.Button>\n            <Radio.Button value={2}>{t('this.week')}</Radio.Button>\n            <Radio.Button value={3}>{t('this.month')}</Radio.Button>\n            <Radio.Button value={4}>{t('this.year')}</Radio.Button>\n            <Radio.Button value={5}>{t('last.year')}</Radio.Button>\n            <Radio.Button value={6}>{t('in.7.days')}</Radio.Button>\n            <Radio.Button value={7}>{t('in.30.days')}</Radio.Button>\n            <Radio.Button value={8}>{t('in.1.year')}</Radio.Button>\n          </Radio.Group>\n        </Space>\n      </Form.Item>\n    </Col>\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemDateRangeWithBreak/index.jsx",
    "content": "import {Col, DatePicker, Form, Radio, Row, Select, Space} from 'antd';\nimport {useEffect, useState} from \"react\";\nimport {radioValueToTimeRange,getTimeFormat,getTimeEnable} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\n\nexport default (props) => {\n\n  const { form, dateRadioValue, setDateRadioValue } = props;\n\n  function createTimeRadioChangeHandler(e) {\n    setDateRadioValue(e.target.value);\n  }\n  useEffect(() => {\n    form.setFieldsValue({\n      'createTimeRange': radioValueToTimeRange(dateRadioValue),\n      'breakType': breakType\n    });\n  }, [dateRadioValue]);\n\n  const [breakType, setBreakType] = useState(\"month\");\n\n  return (\n    <Row gutter={8}>\n      <Col flex=\"600px\">\n        <Form.Item label={t('time.between')}>\n          <Space>\n            <Form.Item name=\"createTimeRange\" noStyle={true}>\n              <DatePicker.RangePicker picker={breakType} allowClear={false} />\n            </Form.Item>\n            <Radio.Group size=\"small\" onChange={createTimeRadioChangeHandler} value={dateRadioValue}>\n              <Radio.Button value={2}>{t('this.week')}</Radio.Button>\n              <Radio.Button value={3}>{t('this.month')}</Radio.Button>\n              <Radio.Button value={4}>{t('this.year')}</Radio.Button>\n              <Radio.Button value={5}>{t('last.year')}</Radio.Button>\n              <Radio.Button value={6}>{t('in.7.days')}</Radio.Button>\n              <Radio.Button value={7}>{t('in.30.days')}</Radio.Button>\n              <Radio.Button value={8}>{t('in.1.year')}</Radio.Button>\n            </Radio.Group>\n          </Space>\n        </Form.Item>\n      </Col>\n      <Col flex=\"250px\">\n        <Form.Item label={t('report.break.down.label')} name=\"breakType\">\n          <Select defaultValue={breakType} value={breakType} onChange={(value) => setBreakType(value)}>\n            <Select.Option value=\"day\">{t('unit.days')}</Select.Option>\n            {/*周的计算有问题，如果一年的第一天不是周一，antd的计算和java不同*/}\n            {/*<Select.Option value=\"week\">{t('unit.weeks')}</Select.Option>*/}\n            <Select.Option value=\"month\">{t('unit.months')}</Select.Option>\n            <Select.Option value=\"quarter\">{t('unit.quarters')}</Select.Option>\n            <Select.Option value=\"year\">{t('unit.years')}</Select.Option>\n          </Select>\n        </Form.Item>\n      </Col>\n    </Row>\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemDescription/index.jsx",
    "content": "import {Form, Input} from 'antd';\nimport {getDescriptionEnable} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  return (\n    getDescriptionEnable() ?\n      <Form.Item label={t('description')} name=\"description\">\n        <Input />\n      </Form.Item> :\n      null\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemDescriptionSearch/index.jsx",
    "content": "import {Col, Form, Input} from 'antd';\nimport {getDescriptionEnable} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  return (\n    getDescriptionEnable() ?\n      <Col flex=\"auto\" style={{minWidth:300}}>\n        <Form.Item label={t('description')} name=\"description\">\n          <Input allowClear={true} />\n        </Form.Item>\n      </Col> :\n      null\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemPayee/index.jsx",
    "content": "import {useState} from \"react\";\nimport {Divider, Form, Input, message, Select} from 'antd';\nimport {useDispatch} from \"umi\";\nimport {PlusOutlined} from \"@ant-design/icons\";\nimport {categoryTypeToCreateParam} from \"@/utils/util\";\nimport {create as createPayee} from \"@/services/payee\";\nimport t from \"@/utils/translate\";\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n  const { form, payees, setPayees, type } = props;\n\n  const [addPayeeName, setAddPayeeName] = useState();\n  function addPayeeInputChange(event) {\n    setAddPayeeName(event.target.value)\n  }\n  const [addPayeeLoading, setAddPayeeLoading] = useState(false);\n  async function addPayeeHandler() {\n    if (addPayeeLoading) return;\n    if (!addPayeeName) return;\n    setAddPayeeLoading(true);\n    const response = await createPayee({...{ name: addPayeeName }, ...categoryTypeToCreateParam(type)});\n    if (response && response.success) {\n      setPayees([...payees, {value: response.data.id, label: response.data.name}]);\n      setAddPayeeName('');\n      form.setFieldsValue({payeeId: response.data.id});\n      dispatch({ type: 'payee/refresh', payload: { ...response.data } });\n    }\n    setAddPayeeLoading(false);\n  }\n\n  return (\n    <Form.Item label={t('flow.payee')} name=\"payeeId\">\n      <Select\n        showSearch={true}\n        allowClear={true}\n        filterOption={true}\n        optionFilterProp={\"label\"}\n        options={payees}\n        dropdownRender={menu => (\n          <div>\n            {menu}\n            <>\n              <Divider style={{ margin: '4px 0' }} />\n              <div style={{ display: 'flex', flexWrap: 'nowrap', padding: 4 }}>\n                <Input style={{ flex: 'auto' }} value={addPayeeName} onChange={addPayeeInputChange} />\n                <a style={{ flex: 'none', padding: '4px', display: 'block', cursor: 'pointer' }} onClick={addPayeeHandler}>\n                  <PlusOutlined /> {t('add')}\n                </a>\n              </div>\n            </>\n          </div>\n        )}\n      >\n      </Select>\n    </Form.Item>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemPayees/index.jsx",
    "content": "import {Col, Form, Select} from 'antd';\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const { data, name = \"payees\", label=t('flow.payee') } = props;\n\n  return (\n    <Col flex=\"auto\" style={{minWidth:300}}>\n      <Form.Item label={label} name={name}>\n        <Select mode=\"multiple\" options={data} allowClear showArrow showSearch filterOption optionFilterProp={\"label\"} />\n      </Form.Item>\n    </Col>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemStatus/index.jsx",
    "content": "import {Col, Form, Select} from 'antd';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  return (\n    <Col flex=\"200px\">\n      <Form.Item label={t('flow.status')} name=\"status\">\n        <Select allowClear={true}>\n          <Select.Option value={1}>{t('flow.status1')}</Select.Option>\n          <Select.Option value={2}>{t('flow.status2')}</Select.Option>\n          <Select.Option value={3}>{t('flow.status3')}</Select.Option>\n        </Select>\n      </Form.Item>\n    </Col>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemTag/AddTagModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Input, message, Modal, Switch, TreeSelect} from 'antd';\nimport { create } from '@/services/tag';\nimport {nameRules, notesRules} from \"@/utils/rules\";\nimport {getNull, validateForm} from \"@/utils/util\";\nimport {useCategoryTreeSelectData} from \"@/utils/hooks\";\nimport t from \"@/utils/translate\";\nimport styles from './index.less';\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const { visible, onHide, type } = props;\n\n  const { getEnableResponse : tagResponse } = useSelector(state => state.tag);\n  const [treeData] = useCategoryTreeSelectData(tagResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!tagResponse) dispatch({ type: 'tag/fetchEnable' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({})\n  useEffect(() => {\n    if (visible) {\n      form.setFieldsValue({\n        ...getNull(form.getFieldsValue()),\n        ...{\n          expenseable: type === 1 ? true : false,\n          incomeable: type === 2 ? true : false,\n          transferable: type === 3 ? true : false,\n          parentId: null,\n        }\n      });\n    }\n  }, [visible]);\n\n  const messageOperationSuccess = t('operation.success');\n  const [confirmLoading, setConfirmLoading] = useState(false);\n  async function okHandler() {\n    setConfirmLoading(true);\n    const values = await validateForm(form);\n    if (values) {\n      const response = await create(values);\n      if (response && response.success) {\n        onHide();\n        message.success(messageOperationSuccess);\n        dispatch({ type: 'tag/refresh', payload: { ...response.data } });\n      }\n    }\n    setConfirmLoading(false);\n  }\n\n  function successHandler(response) {\n    onHide();\n  }\n\n  return (\n    <Modal\n      forceRender={true}\n      maskClosable={false}\n      title={t('new') + t('tag')}\n      visible={visible}\n      onOk={okHandler}\n      onCancel={onHide}\n      confirmLoading={confirmLoading}\n    >\n      <Form form={form} className={styles['form']}>\n        <Form.Item label={t('parent.category')} name=\"parentId\">\n          <TreeSelect\n            allowClear={true}\n            treeDataSimpleMode={true}\n            treeData={treeData}\n            showArrow={true}\n            showSearch={true}\n            treeNodeFilterProp=\"title\"\n            treeDefaultExpandAll={false} />\n        </Form.Item>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('expenseable')} valuePropName=\"checked\" name=\"expenseable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('incomeable')} valuePropName=\"checked\" name=\"incomeable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('transferable')} valuePropName=\"checked\" name=\"transferable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </Modal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemTag/index.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useFocus} from \"@/utils/hooks\";\nimport {useDispatch} from \"umi\";\nimport {Button, Checkbox, Col, Form, Input, message, Row, Tag, TreeSelect,Modal} from 'antd';\nimport {PlusOutlined} from \"@ant-design/icons\";\nimport AddTagModal from './AddTagModal';\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n  const { data, type } = props;\n\n  const normFile = (e) => {\n    if (Array.isArray(e)) {\n      return e.map(i=>i.value);\n    }\n  };\n\n  function addHandler() {\n    setVisible(true);\n  }\n\n  function hide() {\n    setVisible(false);\n  }\n\n  const [visible, setVisible] = useState(false);\n\n  return (\n    <Row gutter={20}>\n      <Col flex=\"auto\">\n        <Form.Item label={t('flow.tag')} name=\"tags\" getValueFromEvent={normFile}>\n          <TreeSelect\n            treeDataSimpleMode={true}\n            treeData={data}\n            multiple={true}\n            treeCheckable={true}\n            treeCheckStrictly={true}\n            labelInValue={false}\n            showArrow={true}\n            showSearch={true}\n            treeNodeFilterProp=\"title\"\n            treeDefaultExpandAll={false} />\n        </Form.Item>\n      </Col>\n      <Col flex=\"50px\" style={{ marginLeft: 'auto' }}>\n        <Button icon={<PlusOutlined />} type=\"link\" onClick={addHandler}>{t('new') + t('flow.tag')}</Button>\n        <AddTagModal visible={visible} onHide={hide} type={type} />\n      </Col>\n    </Row>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemTag/index.less",
    "content": ".form {\n  :global {\n    .ant-form-item-label {\n      width: 65px;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemTagReport/index.jsx",
    "content": "import {Checkbox, Col, Form, TreeSelect} from 'antd';\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const { data, name = \"tags\", label = t('flow.tag') } = props;\n\n  const normFile = (e) => {\n    if (Array.isArray(e)) {\n      return e.map(i=>i.value);\n    }\n  };\n\n  return (\n    <Col flex=\"auto\" style={{minWidth:300}}>\n      <Form.Item label={label} name={name} getValueFromEvent={normFile}>\n        <TreeSelect\n          treeDataSimpleMode={true}\n          treeData={data}\n          treeDefaultExpandAll={false}\n          showCheckedStrategy={TreeSelect.SHOW_ALL}\n          treeCheckStrictly={true}\n          showArrow={true}\n          showSearch={true}\n          allowClear={true}\n          treeNodeFilterProp=\"title\"\n          treeCheckable={true} />\n      </Form.Item>\n    </Col>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormItemTags/index.jsx",
    "content": "import {Checkbox, Col, Form, TreeSelect} from 'antd';\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const { data, name = \"tags\", label = t('flow.tag') } = props;\n\n  return (\n    <Col flex=\"auto\" style={{minWidth:300}}>\n      <Form.Item label={label} name={name}>\n        <TreeSelect\n          treeDataSimpleMode={true}\n          treeData={data}\n          treeDefaultExpandAll={false}\n          showCheckedStrategy={TreeSelect.SHOW_ALL}\n          treeCheckStrictly={false}\n          showArrow={true}\n          showSearch={true}\n          allowClear={true}\n          treeNodeFilterProp=\"title\"\n          treeCheckable={true} />\n      </Form.Item>\n    </Col>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormListCategory/index.jsx",
    "content": "import {Button, Col, Form, Input, Row, Select, TreeSelect, Space} from 'antd';\nimport {MinusCircleOutlined, PlusOutlined, PlusCircleOutlined} from \"@ant-design/icons\";\nimport {categoryRequiredRules, amountRequiredRules} from \"@/utils/rules\";\nimport t from '@/utils/translate';\nimport {useIntl} from \"umi\";\nimport styles from './index.less';\n\nexport default (props) => {\n\n  const { categories, isConvert = false, currency } = props;\n\n  const intl = useIntl();\n  const categoryRequired = categoryRequiredRules();\n  const amountRequired = amountRequiredRules();\n  return (\n    <Form.List name=\"categories\">\n      {(fields, { add, remove }) => {\n        return (\n          <>\n            {fields.map((field) => (\n              <Row key={field.key} gutter={8} className={styles['row']}>\n                <Col flex=\"auto\">\n                  <Form.Item\n                    {...field}\n                    label={intl.formatMessage({id:'category'})}\n                    rules={categoryRequired}\n                    name={[field.name, 'categoryId']}\n                    fieldKey={[field.fieldKey, 'categoryId']}>\n                    <TreeSelect\n                      treeDataSimpleMode={true}\n                      treeData={categories}\n                      showArrow={true}\n                      showSearch={true}\n                      treeNodeFilterProp=\"title\"\n                      treeDefaultExpandAll={true} />\n                  </Form.Item>\n                </Col>\n                <Col flex=\"150px\" className={styles['amount-col']}>\n                  <Form.Item\n                    {...field}\n                    label={intl.formatMessage({id:'amount'})}\n                    labelCol={{span:5}}\n                    rules={amountRequired}\n                    name={[field.name, 'amount']}\n                    fieldKey={[field.fieldKey, 'amount']}>\n                    <Input />\n                  </Form.Item>\n                </Col>\n                {\n                  isConvert ?\n                    <Col flex=\"150px\" className={styles['convert-amount-col']}>\n                      <Form.Item\n                        {...field}\n                        label={currency}\n                        labelCol={{span:9}}\n                        rules={amountRequired}\n                        name={[field.name, 'convertedAmount']}\n                        fieldKey={[field.fieldKey, 'convertedAmount']}>\n                        <Input />\n                      </Form.Item>\n                    </Col> : null\n                }\n                <Col flex=\"25px\">\n                  <Space>\n                    <PlusCircleOutlined onClick={() => add()} />\n                    {fields.length > 1 ? (\n                      <MinusCircleOutlined onClick={() => remove(field.name)} />\n                    ) : null}\n                  </Space>\n                </Col>\n              </Row>\n            ))}\n          </>\n        );\n      }}\n    </Form.List>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormListCategory/index.less",
    "content": ".row {\n  align-items: baseline;\n  .amount-col {\n    :global {\n      .ant-form-item-label {\n        flex: 1 !important;\n        width: auto !important;\n        max-width: none !important;\n      }\n      .ant-form-item-control {\n        flex: 2;\n      }\n    }\n  }\n  .convert-amount-col {\n    :global {\n      .ant-form-item-label {\n        flex: 2 !important;\n        width: auto !important;\n        max-width: none !important;\n      }\n      .ant-form-item-control {\n        flex: 2;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/FormModal/index.jsx",
    "content": "import {useEffect, useState} from 'react';\nimport {useDispatch, useSelector} from \"umi\";\nimport {Modal, Button, message} from \"antd\";\nimport {validateForm} from \"@/utils/util\";\nimport t from '@/utils/translate';\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n\n  const {\n    title,\n    form,\n    initialValues,\n    create,\n    update,\n    refund,\n    onSuccess,\n    onParseValues\n  } = props;\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const messageOperationSuccess = t('operation.success');\n  const [confirmLoading, setConfirmLoading] = useState(false);\n  async function okHandler() {\n    setConfirmLoading(true);\n    const values = await validateForm(form);\n    if (values) {\n      if (values.createTime) values.createTime = values.createTime.valueOf();\n      if (onParseValues) onParseValues(values);\n      // if (type === 2) {\n      //   if (values.accountId) {\n      //     if (currentItem.account.id === values.accountId) delete values.accountId;\n      //   }\n      //   if (values.categories) {\n      //     if (JSON.stringify(currentItem.categories) === JSON.stringify(values.categories)) delete values.categories;\n      //   }\n      //   if (values.tags) {\n      //     if (JSON.stringify(currentItem.tags.map(i => i.tagId)) === JSON.stringify(values.tags)) delete values.tags;\n      //   }\n      //   if (values.fromId) if (currentItem.fromId == values.fromId) delete values.fromId;\n      //   if (values.toId) if (currentItem.toId == values.toId) delete values.toId;\n      // }\n      const response = type === 2 ? await update(currentItem.id, values) : type === 4 ? await refund(currentItem.id, values) : await create(values);\n      if (response && response.success) {\n        cancelHandler();\n        message.success(messageOperationSuccess);\n        onSuccess(response);\n      }\n    }\n    setConfirmLoading(false);\n  }\n\n  function resetHandler() {\n    form.setFieldsValue({...initialValues});\n  }\n\n  useEffect(() => {\n    if (initialValues && Object.keys(initialValues).length > 0) {\n      form.setFieldsValue({...initialValues});\n    }\n  }, [initialValues]);\n\n  function cancelHandler() {\n    dispatch({ type: 'modal/hide' });\n  }\n\n  return (\n    <Modal\n      width={600}\n      forceRender={true}\n      maskClosable={false}\n      title={title}\n      visible={visible}\n      onOk={okHandler}\n      onCancel={cancelHandler}\n      footer={[\n        <Button key=\"back\" onClick={cancelHandler}>{t('form.cancel')}</Button>,\n        <Button key=\"reset\" onClick={resetHandler}>{t('form.reset')}</Button>,\n        <Button key=\"submit\" type=\"primary\" loading={confirmLoading} onClick={okHandler}>{t('form.submit')}</Button>,\n      ]}\n    >\n      {props.children}\n    </Modal>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/HeaderDropdown/index.jsx",
    "content": "import { Dropdown } from 'antd';\nimport React from 'react';\nimport classNames from 'classnames';\nimport styles from './index.less';\n\nconst HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => (\n  <Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />\n);\n\nexport default HeaderDropdown;\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/HeaderDropdown/index.less",
    "content": "@import '~antd/es/style/themes/default.less';\n\n.container > * {\n  background-color: @popover-bg;\n  border-radius: 4px;\n  box-shadow: @shadow-1-down;\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/IncomeModal/index.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport {useDispatch, useSelector} from 'umi';\nimport {Form, Input, Switch} from 'antd';\nimport moment from 'moment';\nimport FormModal from \"@/components/FormModal\";\nimport FormListCategory from '@/components/FormListCategory';\nimport FormItemAccount from \"@/components/FormItemAccount\";\nimport FormItemDescription from '@/components/FormItemDescription';\nimport FormItemCreateTime from '@/components/FormItemCreateTime';\nimport FormItemPayee from '@/components/FormItemPayee';\nimport FormItemTag from '@/components/FormItemTag';\nimport { create, update, refund } from '@/services/income';\nimport {\n  useCategoryTreeSelectData,\n  useResponseData,\n  useResponseSelectData\n} from \"@/utils/hooks\";\nimport {getNull, refreshFlow} from \"@/utils/util\";\nimport t from '@/utils/translate';\nimport styles from './index.less';\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { defaultBook } = useSelector(state => state.session);\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const { getIncomeableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getIncomeableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees, setPayees] = useResponseSelectData(payeeResponse);\n\n  const { getIncomeableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseData(accountResponse);\n\n  const { querySimpleResponse } = useSelector(state => state.incomeCategory);\n  const [categories] = useCategoryTreeSelectData(querySimpleResponse);\n\n  useEffect(() => {\n    if (visible) {\n      if (!defaultBook) dispatch({ type: 'session/fetchSession' });\n      if (!tagResponse) dispatch({ type: 'tag/fetchIncomeable' });\n      if (!payeeResponse) dispatch({ type: 'payee/fetchIncomeable' });\n      if (!accountResponse) dispatch({ type: 'account/fetchIncomeable' });\n      if (!querySimpleResponse) dispatch({ type: 'incomeCategory/fetchSimple' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({})\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        'createTime': moment(),\n        'accountId': defaultBook && defaultBook.defaultIncomeAccount && defaultBook.defaultIncomeAccount.id,\n        'categories': defaultBook && defaultBook.defaultIncomeCategory ? [{ categoryId: defaultBook.defaultIncomeCategory.id }] : [{ }],\n        'tags': [],\n        'confirmed': true,\n      });\n      if (defaultBook && defaultBook.defaultIncomeAccount) {\n        setAccountCurrencyCode(defaultBook.defaultIncomeAccount.currencyCode);\n      } else {\n        setAccountCurrencyCode('');\n      }\n    } else {\n      let currentItemCopy = JSON.parse(JSON.stringify(currentItem));\n      if (type === 4) {\n        if (currentItemCopy.categories) currentItemCopy.categories.forEach(value => {\n          value.amount = value.amount*(-1);\n          value.convertedAmount = value.convertedAmount*(-1)\n        });\n      }\n      if (type === 3 || type === 4) { //不复制备注\n        currentItemCopy.notes = \"\";\n      }\n      // 数字类型的校验存在问题, antd bug\n      if (currentItemCopy.categories) currentItemCopy.categories.forEach(value => {value.amount = value.amount.toString();value.convertedAmount = value.convertedAmount.toString()});\n      currentItemCopy.createTime = type === 2 ? moment(currentItemCopy.createTime) : moment();\n      currentItemCopy.accountId = currentItemCopy.account ? currentItemCopy.account.id : null;\n      currentItemCopy.payeeId = currentItemCopy.payee ? currentItemCopy.payee.id : null;\n      currentItemCopy.tags = currentItemCopy.tags ? currentItemCopy.tags.map(item => item.tagId) : null;\n      currentItemCopy.confirmed = type === 3 ? true : currentItemCopy.status === 1;\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        ...currentItemCopy,\n        'categories': currentItemCopy.categories ? currentItemCopy.categories : [{}],\n      });\n      setAccountCurrencyCode(currentItemCopy.currencyCode);\n    }\n  }, [visible, type, currentItem]);\n\n  function successHandler(response) {\n    refreshFlow(response.data);\n  }\n\n  const [accountCurrencyCode, setAccountCurrencyCode] = useState();\n  const accountChangeHandler = (value) => {\n    for (let i = 0; i < accounts.length; i++) {\n      if (accounts[i].id === value) {\n        setAccountCurrencyCode(accounts[i].currencyCode);\n      }\n    }\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : type === 2 ? t('update') : type === 3 ? t('copy') : t('refund')) + t('income')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      refund={refund}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form form={form} className={styles['form']}>\n        <FormItemDescription />\n        <FormItemCreateTime />\n        <FormItemAccount data={accounts} required={true} onSelectChange={accountChangeHandler} />\n        <FormListCategory categories={categories} isConvert={defaultBook && accountCurrencyCode && defaultBook.defaultCurrencyCode !== accountCurrencyCode} currency={t('convertCurrency')+defaultBook.defaultCurrencyCode} />\n        <FormItemPayee form={form} payees={payees} setPayees={setPayees} type={2} />\n        <FormItemTag data={tags} type={2} />\n        {(type !== 2) &&\n        <Form.Item label={t('flow.isConfirmed')} valuePropName=\"checked\" name=\"confirmed\">\n          <Switch />\n        </Form.Item>\n        }\n        <Form.Item label={t('notes')} name=\"notes\">\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/IncomeModal/index.less",
    "content": ".form {\n  :global {\n    .ant-form-item-label {\n      width: 70px;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/Loading/index.jsx",
    "content": "// 暂时没用到\nimport { Spin } from 'antd';\nimport { LoadingOutlined } from '@ant-design/icons';\n\nexport default () => {\n  const antIcon = <LoadingOutlined style={{ fontSize: 12 }} spin />;\n  return (\n    <Spin size=\"small\" indicator={antIcon} />\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/ModalRoot/index.jsx",
    "content": "import {useSelector} from \"umi\";\n\nexport default () => {\n\n  const { component: Component } = useSelector(state => state.modal);\n\n  return (\n    Component ? <Component /> : null\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/PrimaryMenu/index.jsx",
    "content": "import { Menu } from 'antd';\nimport { NavLink } from 'umi';\nimport { AreaChartOutlined, BankOutlined, AccountBookOutlined, SettingOutlined, CalendarOutlined } from '@ant-design/icons';\nimport t from '@/utils/translate';\n\nimport styles from './index.less';\n\nexport default () => {\n\n  return (\n    <Menu theme={\"dark\"} mode={\"inline\"} defaultOpenKeys={['sub2', 'sub3']} className={styles['menu']}>\n      <Menu.SubMenu key=\"sub1\" icon={<AreaChartOutlined />} title={t('menu.report')}>\n        <Menu.Item key=\"11\"><NavLink to='/dashboard'>{t('menu.overview')}</NavLink></Menu.Item>\n        <Menu.Item key=\"12\"><NavLink to='/reports/expense-category'>{t('menu.expense.category.reports')}</NavLink></Menu.Item>\n        <Menu.Item key=\"13\"><NavLink to='/reports/expense-tag'>{t('menu.expense.tag.reports')}</NavLink></Menu.Item>\n        <Menu.Item key=\"14\"><NavLink to='/reports/income-category'>{t('menu.income.category.reports')}</NavLink></Menu.Item>\n        <Menu.Item key=\"15\"><NavLink to='/reports/income-tag'>{t('menu.income.tag.reports')}</NavLink></Menu.Item>\n        <Menu.Item key=\"16\"><NavLink to='/reports/expense-income-trend'>{t('menu.flow.trend')}</NavLink></Menu.Item>\n        <Menu.Item key=\"17\"><NavLink to='/reports/balance-sheet'>{t('menu.balance.sheet')}</NavLink></Menu.Item>\n        <Menu.Item key=\"18\"><NavLink to='/reports/asset-debt-trend'>{t('menu.asset.trend')}</NavLink></Menu.Item>\n      </Menu.SubMenu>\n      <Menu.SubMenu key=\"sub2\" icon={<BankOutlined />} title={t('menu.account.list')}>\n        <Menu.Item key=\"21\"><NavLink to='/checking-accounts'>{t('menu.checking.account')}</NavLink></Menu.Item>\n        <Menu.Item key=\"22\"><NavLink to='/credit-accounts'>{t('menu.credit.account')}</NavLink></Menu.Item>\n        <Menu.Item key=\"23\"><NavLink to='/debt-accounts'>{t('menu.debt.account')}</NavLink></Menu.Item>\n        <Menu.Item key=\"24\"><NavLink to='/asset-accounts'>{t('menu.asset.account')}</NavLink></Menu.Item>\n        <Menu.Item key=\"26\"><NavLink to='/accounts'>{t('menu.account.overview')}</NavLink></Menu.Item>\n        <Menu.Item key=\"27\"><NavLink to='/balance-logs'>{t('balance.log')}</NavLink></Menu.Item>\n      </Menu.SubMenu>\n      <Menu.SubMenu inlineCollapsed={false} key=\"sub3\" icon={<AccountBookOutlined />} title={t('menu.bookkeeping')}>\n        <Menu.Item key=\"31\"><NavLink to='/expenses'>{t('menu.expense')}</NavLink></Menu.Item>\n        <Menu.Item key=\"32\"><NavLink to='/incomes'>{t('menu.income')}</NavLink></Menu.Item>\n        <Menu.Item key=\"34\"><NavLink to='/transfers'>{t('menu.transfer')}</NavLink></Menu.Item>\n        <Menu.Item key=\"35\"><NavLink to='/flows'>{t('menu.flow')}</NavLink></Menu.Item>\n        <Menu.Item key=\"36\"><NavLink to='/audit'>{t('menu.audit')}</NavLink></Menu.Item>\n      </Menu.SubMenu>\n      <Menu.Item key=\"61\" icon={<CalendarOutlined />}><NavLink to='/items'>提醒管理</NavLink></Menu.Item>\n      <Menu.SubMenu key=\"sub4\" icon={<SettingOutlined />} title={t('menu.settings')}>\n        <Menu.Item key=\"41\"><NavLink to='/categories'>{t('menu.category')}</NavLink></Menu.Item>\n        <Menu.Item key=\"45\"><NavLink to='/books'>{t('menu.book')}</NavLink></Menu.Item>\n        <Menu.Item key=\"46\"><NavLink to='/groups'>{t('menu.group')}</NavLink></Menu.Item>\n        <Menu.Item key=\"42\">{t('menu.security')}</Menu.Item>\n        <Menu.Item key=\"43\">{t('menu.budget')}</Menu.Item>\n        <Menu.Item key=\"44\"><NavLink to='/scheduled'>{t('menu.schedule')}</NavLink></Menu.Item>\n      </Menu.SubMenu>\n    </Menu>\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/PrimaryMenu/index.less",
    "content": ".menu {\n  height: 100%;\n  overflow: hidden auto;\n  font-size: 14px;\n  :global {\n    .ant-menu {\n      font-size: 14px;\n    }\n    .ant-menu-submenu-title {\n      height: 40px !important;\n      line-height: 40px !important;\n    }\n    .ant-menu-item {\n      height: 40px !important;\n      line-height: 40px !important;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/TransferModal/index.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport {useDispatch, useSelector} from 'umi';\nimport { Form, Input, Switch } from 'antd';\nimport moment from 'moment';\nimport { create, update } from '@/services/transfer';\nimport {useCategoryTreeSelectData, useResponseData} from \"@/utils/hooks\";\nimport FormItemDescription from '@/components/FormItemDescription';\nimport FormItemCreateTime from '@/components/FormItemCreateTime';\nimport FormItemAccount from '@/components/FormItemAccount';\nimport FormModal from \"@/components/FormModal\";\nimport FormItemTag from \"@/components/FormItemTag\";\nimport {amountRequiredRulesPositive, requiredRules} from \"@/utils/rules\";\nimport {getNull, refreshFlow} from \"@/utils/util\";\nimport t from '@/utils/translate';\nimport styles from './index.less';\n\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const { defaultBook } = useSelector(state => state.session);\n\n  const { getTransferableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getTransferToAbleResponse } = useSelector(state => state.account);\n  const [accountsTransferTo] = useResponseData(getTransferToAbleResponse);\n\n  const { getTransferFromAbleResponse } = useSelector(state => state.account);\n  const [accountsTransferFrom] = useResponseData(getTransferFromAbleResponse);\n\n  useEffect(() => {\n    if (visible) {\n      if (!defaultBook) dispatch({ type: 'session/fetchSession' });\n      if (!tagResponse) dispatch({ type: 'tag/fetchTransferable' });\n      if (!getTransferToAbleResponse) dispatch({ type: 'account/fetchTransferToAble' });\n      if (!getTransferFromAbleResponse) dispatch({ type: 'account/fetchTransferFromAble' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({})\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      let fromId = null;\n      if (defaultBook && defaultBook.defaultTransferFromAccount) {\n        fromId = defaultBook.defaultTransferFromAccount.id;\n      }\n      let toId = null;\n      if (defaultBook && defaultBook.defaultTransferToAccount) {\n        toId = defaultBook.defaultTransferToAccount.id;\n      }\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        'createTime': moment(),\n        'fromId': fromId,\n        'toId': toId,\n        'tags': [],\n        'confirmed': true,\n      });\n      if (defaultBook && defaultBook.defaultTransferFromAccount) {\n        setFromCurrencyCode(defaultBook.defaultTransferFromAccount.currencyCode);\n      } else {\n        setFromCurrencyCode('');\n      }\n      if (defaultBook && defaultBook.defaultTransferToAccount) {\n        setToCurrencyCode(defaultBook.defaultTransferToAccount.currencyCode);\n      } else {\n        setToCurrencyCode('');\n      }\n    } else {\n      let currentItemCopy = JSON.parse(JSON.stringify(currentItem));\n      if (type === 3) { //不复制备注\n        currentItemCopy.notes = \"\";\n      }\n      // 数字类型的校验存在问题, antd bug\n      currentItemCopy.amount = currentItemCopy.amount.toString();\n      currentItemCopy.createTime = type === 3 ? moment() : moment(currentItemCopy.createTime);\n      currentItemCopy.tags = currentItemCopy.tags ? currentItemCopy.tags.map(item => item.tagId) : null;\n      currentItemCopy.confirmed = type === 3 ? true : currentItemCopy.status === 1;\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        ...currentItemCopy\n      });\n      setFromCurrencyCode(currentItemCopy.currencyCode);\n      setToCurrencyCode(currentItemCopy.toCurrencyCode);\n    }\n  }, [visible, type, currentItem]);\n\n  function successHandler(response) {\n    refreshFlow(response.data);\n  }\n\n  const [fromCurrencyCode, setFromCurrencyCode] = useState();\n  const [toCurrencyCode, setToCurrencyCode] = useState();\n  const fromAccountChangeHandler = (value) => {\n    for (let i = 0; i < accountsTransferFrom.length; i++) {\n      if (accountsTransferFrom[i].id === value) {\n        setFromCurrencyCode(accountsTransferFrom[i].currencyCode);\n      }\n    }\n  }\n  const toAccountChangeHandler = (value) => {\n    for (let i = 0; i < accountsTransferTo.length; i++) {\n      if (accountsTransferTo[i].id === value) {\n        setToCurrencyCode(accountsTransferTo[i].currencyCode);\n      }\n    }\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : type === 3 ? t('copy') : t('update')) + t('transfer')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form form={form} className={styles['form']}>\n        <FormItemDescription />\n        <FormItemCreateTime />\n        <FormItemAccount data={accountsTransferFrom} label={t('transfer.from.account')} name=\"fromId\" onSelectChange={fromAccountChangeHandler} />\n        <FormItemAccount data={accountsTransferTo} label={t('transfer.to.account')} name=\"toId\" onSelectChange={toAccountChangeHandler} />\n        <Form.Item label={t('amount')} name=\"amount\" rules={amountRequiredRulesPositive()}>\n          <Input />\n        </Form.Item>\n        {\n          fromCurrencyCode && toCurrencyCode && fromCurrencyCode !== toCurrencyCode ?\n            <Form.Item label={t('convertCurrency')+toCurrencyCode} name=\"convertedAmount\" rules={requiredRules()}>\n              <Input />\n            </Form.Item> : null\n        }\n        <FormItemTag data={tags} type={3} />\n        {(type !== 2) &&\n        <Form.Item label={t('flow.isConfirmed')} valuePropName=\"checked\" name=\"confirmed\">\n          <Switch />\n        </Form.Item>\n        }\n        <Form.Item label={t('notes')} name=\"notes\">\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/TransferModal/index.less",
    "content": ".form {\n  :global {\n    .ant-form-item-label {\n      width: 70px;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/charts/Bar/index.jsx",
    "content": "import { Axis, Chart, Geom, Tooltip } from \"bizcharts\";\nimport {Empty, Spin} from \"antd\";\n\nexport default (props) => {\n\n  const { data, loading = false } = props;\n\n  return (\n    <Spin spinning={loading} size=\"large\">\n      {data && data.length > 0 ?\n        <Chart height={400} data={data} forceFit padding={[50, 30, 20, 40]}>\n          <Axis name=\"x\"/>\n          <Axis name=\"y\"/>\n          <Geom type=\"interval\" position=\"x*y\"/>\n          <Tooltip\n            showTitle={false}\n            itemTpl={`<li data-index={index} style=\"padding-bottom:15px\">{value}</li>`}\n          />\n        </Chart> :\n        <Empty style={{paddingTop: 50, width: \"100%\"}}/>\n      }\n    </Spin>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/charts/Bar/index.less",
    "content": ""
  },
  {
    "path": "bookkeeping-user-fe/src/components/charts/Line/index.jsx",
    "content": "import {Axis, Chart, Geom, Legend, Tooltip} from \"bizcharts\";\nimport {Spin} from \"antd\";\n\nexport default (props) => {\n\n  const { data, scale, height, padding, loading = false } = props;\n\n  return (\n    <Spin spinning={loading} size=\"large\">\n      <Chart data={data} scale={scale} forceFit height={height} padding={padding}>\n        <Legend />\n        <Axis name=\"x1\" label={{rotate: 30, offset: 20}} />\n        <Axis name=\"y\" />\n        <Tooltip crosshairs={{ type: \"y\" }} />\n        <Geom type=\"line\" position=\"x1*y\" size={2} color=\"x2\" />\n        <Geom type=\"point\" position=\"x1*y\" size={4} shape=\"circle\" color=\"x2\" style={{ stroke: \"#fff\", lineWidth: 1 }} />\n      </Chart>\n    </Spin>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/charts/Line/index.less",
    "content": ""
  },
  {
    "path": "bookkeeping-user-fe/src/components/charts/Pie/index.jsx",
    "content": "import { useEffect, useRef, useState } from \"react\";\nimport { Empty, Spin, Divider } from \"antd\";\nimport { Chart, Geom, Coord, Tooltip, Label, Guide } from \"bizcharts\";\nimport { DataView } from '@antv/data-set';\nimport t from '@/utils/translate';\nimport styles from './index.less';\n\n\nexport default (props) => {\n\n  const { data, loading = false } = props;\n\n  const dv = new DataView();\n  dv.source(data).transform({\n    type: 'percent',\n    field: 'y',\n    dimension: 'x',\n    as: 'percent',\n  });\n\n  const tooltipFormat = [\n    'x*y',\n    (x, y) => ({\n      name: x,\n      value: y,\n    }),\n  ];\n\n  const legendClickHandler = (item, i) => {\n    const newItem = item;\n    newItem.checked = !newItem.checked;\n    const newLegendData = [...legendData];\n    newLegendData[i] = newItem;\n    const filteredLegendData = newLegendData.filter((l) => l.checked).map((l) => l.x);\n    if (chartRef.current) {\n      chartRef.current.filter('x', (val) => filteredLegendData.indexOf(`${val}`) > -1);\n    }\n    setLegendData(newLegendData);\n  };\n\n  const chartRef = useRef(null);\n  const getG2Instance = (chart) => {\n    chartRef.current = chart;\n  };\n  useEffect(() => {\n    getLegendData();\n  }, [data]);\n  const [legendData, setLegendData] = useState([]);\n  const getLegendData = () => {\n    if (!chartRef || !chartRef.current) return;\n    const geom = chartRef.current.getAllGeoms()[0]; // 获取所有的图形\n    if (!geom) return;\n    const items = geom.get('dataArray') || []; // 获取图形对应的\n    const legendData = items.map((item) => {\n      /* eslint no-underscore-dangle:0 */\n      const origin = item[0]._origin;\n      origin.color = item[0].color;\n      origin.checked = true;\n      return origin;\n    });\n    setLegendData(legendData);\n  };\n\n  const totalMessage = t('gross.amount');\n  const total = data.reduce((pre, now) => now.y + pre, 0).toFixed(2);\n  return (\n    <Spin spinning={loading} size=\"large\">\n      <div className={styles['pie']}>\n      { data && data.length > 0  ?\n        (\n          <>\n            <Chart className={styles['chart']} height={400} data={dv} forceFit padding={[10, 40, 10, 40 ]} onGetG2Instance={getG2Instance}>\n              <Coord type=\"theta\" radius={0.7} innerRadius={0.65} />\n              <Tooltip showTitle={false} />\n              <Geom type=\"intervalStack\" position=\"percent\" color=\"x\" tooltip={ tooltipFormat }>\n                <Label content=\"percent\" formatter={(val, x) => `${x.point.x}: ${(val * 100).toFixed(2)}%`} />\n              </Geom>\n              <Guide>\n                <Guide.Html\n                  position={[\"50%\", \"50%\"]}\n                  html={`<div style=\"color:#8c8c8c;font-size:0.7rem;text-align: center;width: 10em;\">${totalMessage}<br><span style=\"color:rgba(0,0,0,0.85);font-size:1.5rem;\">${total}</span></div>`}\n                  alignX=\"middle\"\n                  alignY=\"middle\"\n                />\n              </Guide>\n            </Chart>\n            <ul className={styles['legend']}>\n              {legendData.map((item, i) => (\n              <li key={item.x} onClick={() => legendClickHandler(item, i)}>\n                <span className={styles.dot} style={{backgroundColor: !item.checked ? '#aaa' : item.color}} />\n                <span className={styles.legendTitle}>{item.x}</span>\n                <Divider type=\"vertical\" />\n                <span className={styles.percent}>{`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}</span>\n                <span>&nbsp;</span>\n                <span className={styles.value}>{item.y}</span>\n              </li>\n              ))}\n            </ul>\n          </>\n        ) :\n        (<Empty style={{ paddingTop:50, width:\"100%\" }} />)\n      }\n      </div>\n    </Spin>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/charts/Pie/index.less",
    "content": "@import '~antd/es/style/themes/default.less';\n\n.pie {\n  display: flex;\n  flex-flow: row;\n  align-items: center;\n  .chart {\n    flex: 1;\n  }\n  .legend {\n    margin: 0;\n    padding: 0;\n    width: 250px;\n    list-style: none;\n    li {\n      display: flex;\n      align-items: center;\n      margin: 8px 0;\n      cursor: pointer;\n      .dot {\n        display: inline-block;\n        width: 8px;\n        height: 8px;\n        border-radius: 50%;\n      }\n      .legendTitle {\n        flex: 1;\n        padding-left: 8px;\n        color: @text-color;\n      }\n      .percent {\n        width: 55px;\n        color: @text-color-secondary;\n      }\n      .value {\n        width: 65px;\n      }\n    }\n  }\n}\n\n@media (max-width: 1800px){\n  .pie {\n    flex-flow: column;\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/charts/Pie2/index.jsx",
    "content": "import { useEffect, useRef, useState } from \"react\";\nimport { Empty, Spin, Divider } from \"antd\";\nimport { Chart, Geom, Coord, Tooltip, Label, Guide } from \"bizcharts\";\nimport { DataView } from '@antv/data-set';\nimport t from '@/utils/translate';\nimport styles from './index.less';\n\n\nexport default (props) => {\n\n  const { data, loading = false } = props;\n\n  const dv = new DataView();\n  dv.source(data).transform({\n    type: 'percent',\n    field: 'y',\n    dimension: 'x',\n    as: 'percent',\n  });\n\n  const tooltipFormat = [\n    'x*y',\n    (x, y) => ({\n      name: x,\n      value: y,\n    }),\n  ];\n\n  const legendClickHandler = (item, i) => {\n    const newItem = item;\n    newItem.checked = !newItem.checked;\n    const newLegendData = [...legendData];\n    newLegendData[i] = newItem;\n    const filteredLegendData = newLegendData.filter((l) => l.checked).map((l) => l.x);\n    if (chartRef.current) {\n      chartRef.current.filter('x', (val) => filteredLegendData.indexOf(`${val}`) > -1);\n    }\n    setLegendData(newLegendData);\n  };\n\n  const chartRef = useRef(null);\n  const getG2Instance = (chart) => {\n    chartRef.current = chart;\n  };\n  useEffect(() => {\n    getLegendData();\n  }, [data]);\n  const [legendData, setLegendData] = useState([]);\n  const getLegendData = () => {\n    if (!chartRef || !chartRef.current) return;\n    const geom = chartRef.current.getAllGeoms()[0]; // 获取所有的图形\n    if (!geom) return;\n    const items = geom.get('dataArray') || []; // 获取图形对应的\n    const legendData = items.map((item) => {\n      /* eslint no-underscore-dangle:0 */\n      const origin = item[0]._origin;\n      origin.color = item[0].color;\n      origin.checked = true;\n      return origin;\n    });\n    setLegendData(legendData);\n  };\n\n  const totalMessage = t('gross.amount');\n  const total = data.reduce((pre, now) => now.y + pre, 0).toFixed(2);\n  return (\n    <Spin spinning={loading} size=\"large\">\n      <div className={styles['pie']}>\n      { data && data.length > 0 && total > 0 ?\n        (\n          <>\n            <Chart className={styles['chart']} height={450} data={dv} forceFit padding={[10, 120, 10, 120]} onGetG2Instance={getG2Instance}>\n              <Coord type=\"theta\" radius={0.9} innerRadius={0.75} />\n              <Tooltip showTitle={false} />\n              <Geom type=\"intervalStack\" position=\"percent\" color=\"x\" tooltip={ tooltipFormat }>\n                <Label content=\"percent\" formatter={(val, x) => `${x.point.x}: ${(val * 100).toFixed(2)}%`} />\n              </Geom>\n              <Guide>\n                <Guide.Html\n                  position={[\"50%\", \"50%\"]}\n                  html={`<div style=\"color:#8c8c8c;font-size:1.5rem;text-align: center;width: 10em;\">${totalMessage}<br><span style=\"color:rgba(0,0,0,0.85);font-size:2.4rem;\">${total}</span></div>`}\n                  alignX=\"middle\"\n                  alignY=\"middle\"\n                />\n              </Guide>\n            </Chart>\n            <ul className={styles['legend']}>\n              {legendData.map((item, i) => (\n              <li key={item.x} onClick={() => legendClickHandler(item, i)}>\n                <span className={styles.dot} style={{backgroundColor: !item.checked ? '#aaa' : item.color}} />\n                <span className={styles.legendTitle}>{item.x}</span>\n                <Divider type=\"vertical\" />\n                <span className={styles.percent}>{`${(Number.isNaN(item.percent) ? 0 : item.percent * 100).toFixed(2)}%`}</span>\n                <span>&nbsp;</span>\n                <span className={styles.value}>{item.y}</span>\n              </li>\n              ))}\n            </ul>\n          </>\n        ) :\n        (<Empty style={{ paddingTop:50, width:\"100%\" }} />)\n      }\n      </div>\n    </Spin>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/components/charts/Pie2/index.less",
    "content": "@import '~antd/es/style/themes/default.less';\n\n.pie {\n  display: flex;\n  flex-flow: row;\n  align-items: center;\n  .chart {\n    flex: 1;\n  }\n  .legend {\n    margin: 0;\n    padding: 0;\n    width: 240px;\n    min-width: 220px;\n    list-style: none;\n    li {\n      display: flex;\n      align-items: center;\n      margin: 8px 0;\n      cursor: pointer;\n      .dot {\n        display: inline-block;\n        width: 8px;\n        height: 8px;\n        border-radius: 50%;\n      }\n      .legendTitle {\n        flex: 1;\n        padding-left: 8px;\n        color: @text-color;\n      }\n      .percent {\n        width: 55px;\n        color: @text-color-secondary;\n      }\n      .value {\n        width: 65px;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/global.less",
    "content": "@import '~antd/lib/style/themes/default.less';\n\nhtml, body, #root {\n  height: 100%;\n}\n\nbody {\n  margin: 0;\n}\n/*\n.ant-table table {\n  text-align: center !important;\n}\n.ant-table-thead > tr > th {\n  text-align: center !important;\n}*/\n\n.table-color-default {\n  background-color: @blue-3 !important;\n}\n.ant-table-tbody > tr.table-color-default:hover > td {\n  background: unset;\n}\n\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/layouts/BasicLayout.jsx",
    "content": "import {useState} from \"react\";\nimport { Layout, Row, Col, Badge } from 'antd';\nimport {SelectLang} from 'umi';\nimport { MenuFoldOutlined, MenuUnfoldOutlined, QuestionCircleOutlined, MessageOutlined } from '@ant-design/icons';\nimport Avatar from '@/components/AvatarDropdown';\nimport PrimaryMenu from '@/components/PrimaryMenu';\nimport Footer from '@/components/Footer';\nimport ModalRoot from '@/components/ModalRoot';\nimport FlowButtons from '@/components/FlowButtons';\nimport styles from './BasicLayout.less';\n\nexport default (props) => {\n\n  const [collapsed, setCollapsed] = useState(false);\n\n  return (\n    <Layout style={{ minHeight: '100vh' }} className={styles['page-layout']}>\n      <Layout.Sider trigger={null} collapsible collapsed={collapsed} className={styles['layout-sider']}>\n        <div className={styles['logo']}>九快计账</div>\n        <PrimaryMenu />\n      </Layout.Sider>\n      <Layout>\n        <Layout.Header className={styles['header']}>\n          <Row>\n            <Col flex=\"1 1 0%\" style={{textAlign: 'center'}}>\n              <div className={styles['button']} onClick={()=>setCollapsed(!collapsed)}>\n                {collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}\n              </div>\n              <FlowButtons />\n            </Col>\n            <Col style={{paddingRight: 20}}>\n              <a className={styles['button']} href=\"http://docs.jz.jiukuaitech.com/\" target=\"_blank\"><QuestionCircleOutlined /></a>\n              {/*<div className={styles['button']}>*/}\n              {/*  <Badge count={11} style={{ boxShadow: 'none'}}>*/}\n              {/*    <MessageOutlined style={{fontSize: 16, padding: \"5px 5px 5px 0\"}} />*/}\n              {/*  </Badge>*/}\n              {/*</div>*/}\n              <Avatar menu />\n              <SelectLang />\n            </Col>\n          </Row>\n        </Layout.Header>\n        <Layout.Content className={styles['content']}>{props.children}</Layout.Content>\n        <Layout.Footer><Footer /></Layout.Footer>\n        <ModalRoot />\n      </Layout>\n    </Layout>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/layouts/BasicLayout.less",
    "content": ".logo {\n  position: sticky;\n  top: 0;\n  z-index: 999;\n  height: 48px;\n  background: #1890ff;\n  color:#fff;\n  font-size: 25px;\n  text-align: center;\n  line-height: 48px;\n  font-style: italic;\n  font-weight: bold;\n}\n.header {\n  position: sticky;\n  top: 0;\n  z-index: 999;\n  width: 100%;\n  height: 48px;\n  line-height: 48px;\n  background: #fff;\n  padding: 0;\n  .button {\n    float: left;\n    display: inline-block;\n    height: 100%;\n    padding: 0 20px;\n    cursor: pointer;\n    transition: color 0.3s;\n    font-size: 16px;\n    vertical-align: middle;\n    &:hover {\n      background: rgba(0, 0, 0, 0.025);\n      color: #1890ff;\n    }\n  }\n}\n.page-layout {\n  padding-left: 200px;\n}\n.layout-sider {\n  position: fixed !important;\n  top: 0;\n  left: 0;\n  z-index: 999;\n  width: 200px;\n  height: 100%;\n}\n.content {\n  padding: 12px;\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/layouts/UserLayout.jsx",
    "content": "import { Layout } from \"antd\";\nimport Footer from '@/components/Footer';\nimport logo from '@/assets/logo.svg';\nimport t from '@/utils/translate';\nimport styles from './UserLayout.less';\n\nexport default (props) => {\n\n  return (\n    <Layout className={styles['container']}>\n      <Layout.Content className={styles['content']}>\n        <div className={styles['top']}>\n          <div className={styles['header']}>\n            <img alt=\"logo\" className={styles['logo']} src={logo} />\n            <span className={styles['title']}>Ant Design</span>\n          </div>\n          <div className={styles['desc']}>{t('app.title')}</div>\n        </div>\n        { props.children }\n      </Layout.Content>\n      <Layout.Footer><Footer /></Layout.Footer>\n    </Layout>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/layouts/UserLayout.less",
    "content": "@import '~antd/es/style/themes/default.less';\n\n.container {\n  display: flex;\n  flex-direction: column;\n  height: 100vh;\n  overflow: auto;\n  background: @layout-body-background;\n}\n\n.lang {\n  width: 100%;\n  height: 40px;\n  line-height: 44px;\n  text-align: right;\n  :global(.ant-dropdown-trigger) {\n    margin-right: 24px;\n  }\n}\n\n.content {\n  flex: 1;\n  padding: 32px 0;\n}\n\n@media (min-width: @screen-md-min) {\n  .container {\n    background-image: url('https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg');\n    background-repeat: no-repeat;\n    background-position: center 110px;\n    background-size: 100%;\n  }\n\n  .content {\n    padding: 32px 0 24px;\n  }\n}\n\n.top {\n  text-align: center;\n}\n\n.header {\n  height: 44px;\n  line-height: 44px;\n  a {\n    text-decoration: none;\n  }\n}\n\n.logo {\n  height: 44px;\n  margin-right: 16px;\n  vertical-align: top;\n}\n\n.title {\n  position: relative;\n  top: 2px;\n  color: @heading-color;\n  font-weight: 600;\n  font-size: 33px;\n  font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif;\n}\n\n.desc {\n  margin-top: 12px;\n  margin-bottom: 40px;\n  color: @text-color-secondary;\n  font-size: @font-size-base;\n}\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/layouts/index.js",
    "content": "import UserLayout from './UserLayout';\nimport BasicLayout from './BasicLayout';\n\nexport default (props) => {\n  if (props.location.pathname === '/signin' || props.location.pathname === '/register') {\n    return <UserLayout>{ props.children }</UserLayout>\n  }\n  return <BasicLayout>{ props.children }</BasicLayout>\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/account.js",
    "content": "export default {\n\n  'account': 'Account',\n  'checking.account': 'Checking Account',\n\n  'credit.limit': 'Credit Limit',\n  'remain.limit': 'Remain Limit',\n\n  'account.balance': 'Balance',\n  'account.include': 'Included',\n  'expenseable': 'Expense-Able',\n  'incomeable': 'Income-Able',\n  'transferFromAble': 'Transfer From - Able',\n  'transferToAble': 'Transfer To - Able',\n  'account.initial.balance': 'Initial Balance',\n  'account.limit': 'Limit',\n  'remain.limit': 'Remain Limit',\n  'account.billDay': 'Bill Day',\n  'account.apr': 'APR',\n  'account.asOfDate': 'As of Date',\n  'account.card.no': 'Card No',\n\n  'account.current.balance': 'Current Balance',\n\n  'account.log.record': 'Account Logs',\n  'account.new': 'Account Created',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/book.js",
    "content": "export default {\n  'book': 'Book',\n\n  'book.name': 'Name',\n  'book.group': 'Group',\n  'book.description.eable': 'Book Description Enable',\n  'book.time.eable': 'Book Time Enable',\n  'book.default.expense.account': 'Default Expense Account',\n  'book.default.income.account': 'Default Income Account',\n  'book.default.transfer.from.account': 'Default Transfer From Account',\n  'book.default.transfer.to.account': 'Default Transfer To Account',\n  'book.default.expense.category': 'Default Expense Category',\n  'book.default.income.category': 'Default Income Category',\n\n  'set.default': 'Set As Default'\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/category.js",
    "content": "export default {\n\n  'category.expense.category': 'Expense Categories',\n  'category.income.category': 'Income Categories',\n  'category.tag': 'Tags',\n  'category.payee': 'Payees',\n\n  'category.add.expense.category': 'Add Expense Category',\n  'category.update.expense.category': 'Update Expense Category',\n\n  'category.add.income.category': 'Add Income Category',\n  'category.update.income.category': 'Update Income Category',\n\n  'category.add.tag': 'Add Tag',\n  'category.update.tag': 'Update Tag',\n\n  'category.add.payee': 'Add Payee',\n  'category.update.payee': 'Update Payee',\n\n  'category.parent.category': 'Parent Category',\n  'category.name': 'Name',\n\n  'expenseable': 'Expense-Able',\n  'incomeable': 'Income-Able',\n  'transferable': 'Transfer-Able',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/common.js",
    "content": "export default {\n  'app.title': 'BookKeeping System',\n\n  'yes': 'Yes',\n  'no': 'No',\n  'null': 'Unknown',\n\n  'add': 'Add',\n  'new': 'New',\n  'search': 'Search',\n  'update': 'Update',\n  'disable': 'Disable',\n  'enable': 'Enable',\n  'delete': 'Delete',\n  'config': 'Config',\n\n  'form.cancel': 'Cancel',\n  'form.reset': 'Reset',\n  'form.submit': 'Submit',\n\n  'today': 'Today',\n  'this.week': 'This Week',\n  'this.month': 'This Month',\n  'this.year': 'This Year',\n  'last.year': 'Last Year',\n  'in.7.days': 'In 7 Days',\n  'in.30.days': 'In 30 Days',\n  'in.1.year': 'In 1 Year',\n\n  'operation.success': 'Your operation has already succeeded',\n  'delete.confirm': 'Are you sure to delete {name}?',\n\n  'name': 'Name',\n  'is.enable': 'Enable',\n  'notes': 'Notes',\n  'operation': 'Operation',\n\n  'pagination.showTotal': '{range0}-{range1} of {total} items',\n\n  'unit.times': 'Times',\n  'unit.days': 'Days',\n  'unit.weeks': 'Weeks',\n  'unit.months': 'Months',\n  'unit.quarters': 'Quarters',\n  'unit.years': 'Years',\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/dashboard.js",
    "content": "export default {\n  'asset': 'Asset',\n  'debt': 'Debt',\n  'net.worth': 'Net Worth',\n  'frequency.expense': 'Expense Frequency',\n  'frequency.income': 'Income Frequency',\n  'income.expense.table': 'Income Expense Table',\n  'expense.category': 'Expense Categories',\n  'expense.tag': 'Expense Tags',\n  'expense.trend': 'Expense Trend',\n  'income.category': 'Income Categories',\n  'income.trend': 'Income Trend',\n\n  'gross.amount': 'Total Amount',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/flow.js",
    "content": "export default {\n\n  'expense': 'Expense',\n  'income': 'Income',\n  'balance': 'Balance',\n  'initialBalance': 'Opening Balance',\n  'includeAsset': 'Included in Assets',\n  'transfer': 'Transfer',\n  'transfer.from': 'Transfer From',\n  'transfer.to': 'Transfer To',\n  'adjust.balance': 'Adjust Balance',\n\n  'gross.balance': 'Balance in Gross',\n  'gross.expense': 'Expense in Gross',\n  'gross.income': 'Income in Gross',\n  'gross.surplus': 'Surplus in Gross',\n  'gross.transfer.from': 'Transfer From in Gross',\n  'gross.transfer.to': 'Transfer To in Gross',\n\n  'delete.confirm.balance': 'Are you sure you want to delete this transaction? It will also change your account balance.',\n  'flow.refund.confirm': 'Are you sure to refund?',\n  'confirm.operation': 'Are you sure to proceed this operation?',\n\n  'description': 'Description',\n  'amount': 'Amount',\n  'category': 'Category',\n  'flow.tag': 'Tag',\n  'flow.payee': 'Payee',\n  'flow.createTime': 'Time',\n  'flow.status': 'Status',\n  'flow.type': 'Type',\n  'flow.isConfirmed': 'is Confirmed',\n\n  'flow.adjust.balance.modal.title': 'Adjust Balance - {name}',\n\n  'flow.expense.add': 'Add Expense',\n  'flow.expense.copy': 'Copy Expense',\n  'flow.expense.update': 'Update Expense',\n\n  'flow.income.add': 'Add Income',\n  'flow.income.copy': 'Copy Income',\n  'flow.income.update': 'Update Income',\n\n  'flow.transfer.add': 'Add Income',\n  'flow.transfer.update': 'Update Income',\n  'transfer.from.account': 'From Account',\n  'transfer.to.account': 'To Account',\n\n  'update.tag.amount': 'Change Tag Amount',\n  'tag.amount': 'Tag Amount',\n\n  'flow.new.category': 'New Category',\n  'flow.new.tag': 'New Tag',\n\n  'flow.copy': 'Copy',\n  'flow.confirm': 'Confirm',\n  'flow.refund': 'Refund',\n\n  'amount.between': 'Amount Range',\n  'time.between': 'Time Range',\n\n  'flow.status1': 'Normal',\n  'flow.status2': 'Not Confirmed',\n  'flow.status3': 'Refunded',\n\n  'flow.record': 'Flow Record',\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/footer.js",
    "content": "export default {\n  'footer.download.ios': 'Download iOS',\n  'footer.download.andriod': 'Download Android',\n  'footer.download.weibo': 'Facebook',\n  'footer.download.zhihu': 'Twitter',\n  'footer.download.contact': 'Contact US',\n  'footer.download.about': 'About US',\n  'company.name': '武汉九快科技有限公司',\n  'footer.no': '鄂ICP备2021015799号-1',\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/group.js",
    "content": "export default {\n  'group': 'Group',\n\n  'group.name': 'Name',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/header.js",
    "content": "export default {\n  'user.profile': 'User Profile',\n  'user.settings': 'Settings',\n  'user.logout': 'Logout',\n  'user.login': 'Signin',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/menu.js",
    "content": "export default {\n  'menu.report': 'Report',\n  'menu.overview': 'Overview',\n  'menu.expense.category.reports': 'Expense Category Reports',\n  'menu.expense.tag.reports': 'Expense Tag Reports',\n  'menu.income.category.reports': 'Income Category Reports',\n  'menu.income.tag.reports': 'Income Tag Reports',\n  'menu.balance.sheet': 'Balance Sheet',\n  'menu.flow.trend': 'Flow Trend',\n  'menu.asset.trend': 'Asset Trend',\n  'menu.account.list': 'Account List',\n  'menu.checking.account': 'Checking Account',\n  'menu.credit.account': 'Credit Account',\n  'menu.debt.account': 'Debt Account',\n  'menu.asset.account': 'Asset Account',\n  'menu.account.overview': 'Account Overview',\n  'menu.bookkeeping': 'Bookkeeping',\n  'menu.expense': 'Expense',\n  'menu.income': 'Income',\n  'menu.transfer': 'Transfer',\n  'menu.flow': 'Flow',\n  'menu.settings': 'Settings',\n  'menu.category': 'Categories',\n  'menu.book': 'Books',\n  'menu.group': 'Groups',\n  'menu.security': 'Security',\n  'menu.budget': 'Budgets',\n  'menu.schedule': 'Schedules',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/register.js",
    "content": "export default {\n  'register.success': 'Rigister Success!',\n  'placeholder.userName': '1-16 length',\n  'placeholder.password': '6-32 length',\n  'placeholder.invite.code': 'Invitation Code',\n  'placeholder.email': '',\n  'register': 'Register',\n  'register.signin': 'Has Account, Signin',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/report.js",
    "content": "export default {\n  'report.break.down.label': 'Breakdown By',\n  'report.expense.condition': 'Expense Condition',\n  'report.income.condition': 'Income Condition',\n\n  'report.asset.category': 'Asset Category',\n  'report.debt.category': 'Debt Category',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/rules.js",
    "content": "export default {\n  'rules.required': 'Required!',\n\n  'rules.userName.required': 'Please type user name.',\n  'rules.userName.pattern': 'Please type 1-16 length number or alphabet.',\n\n  'rules.password.required': 'Please type password.',\n  'rules.password.pattern': 'Please type 6-32 length',\n\n  'rules.email.pattern': 'Please type email',\n\n  'rules.name.required': 'Please type name.',\n  'rules.name.pattern': 'The length can\\'t be more than 16.',\n\n  'rules.balance.required': 'Please type balance.',\n  'rules.balance.pattern': 'The length can\\'t be more than 9.',\n\n  'rules.amount.required': 'Please type amount.',\n  'rules.amount.pattern': 'The length can\\'t be more than 9.',\n\n  'rules.description.pattern': 'The length can\\'t be more than 16.',\n\n  'rules.notes.pattern': 'The length can\\'t be more than 128.',\n\n  'rules.time.required': 'Please select time.',\n\n  'rules.category.required': 'Please select category.',\n\n  'rules.account.required': 'Please select account.',\n\n  'rules.limit.required': 'Please type credit limit.',\n  'rules.limit.pattern': 'The length can\\'t be more than 9.',\n\n  'rules.apr.pattern': 'The length can\\'t be more than 3.',\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/schedule.js",
    "content": "export default {\n  'schedule.expense.schedule': 'Expense Schedule',\n  'schedule.income.schedule': 'Income Schedule',\n  'schedule.transfer.schedule': 'Transfer Schedule',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US/signin.js",
    "content": "export default {\n  'signin.success': 'Login Success!',\n  'signin': 'Login',\n  'placeholder.userName': '1-16 length',\n  'placeholder.password': '6-32 length',\n  'signin.keep30': 'Keep for 30 days',\n  'signin.forget': 'Can\\'t Remember Password',\n  'register': 'Register New Account',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/en-US.js",
    "content": "import common from './en-US/common';\nimport menu from './en-US/menu';\nimport header from './en-US/header';\nimport footer from './en-US/footer';\nimport signin from './en-US/signin';\nimport register from './en-US/register';\nimport dashboard from './en-US/dashboard';\nimport account from './en-US/account';\nimport flow from './en-US/flow';\nimport category from './en-US/category';\nimport rules from './en-US/rules';\nimport report from './en-US/report';\nimport group from './en-US/group';\nimport book from './en-US/book';\nimport schedule from './en-US/schedule';\n\nexport default {\n  ...common,\n  ...menu,\n  ...header,\n  ...footer,\n  ...signin,\n  ...register,\n  ...dashboard,\n  ...account,\n  ...flow,\n  ...category,\n  ...rules,\n  ...report,\n  ...group,\n  ...book,\n  ...schedule,\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/account.js",
    "content": "export default {\n  'account': '账户',\n  'checking.account': '活期账户',\n  'credit.account': '信用账户',\n  'debt.account': '贷款账户',\n  'asset.account': '资产账户',\n\n  'credit.limit': '信用额度',\n  'remain.limit': '剩余额度',\n  'debt.limit': '贷款额度',\n\n  'account.balance': '余额',\n  'account.include': '计入净资产',\n  'account.initial.balance': '初始余额',\n  'account.billDay': '账单日',\n  'account.apr': '年化利率(%)',\n  'account.asOfDate': '更新日期',\n  'account.card.no': '卡号',\n\n  'account.current.balance': '当前余额',\n\n  'balance.log': '资产日志',\n  'asset.balance': '资产金额',\n  'debt.balance': '负债金额',\n\n  'currency': '币种',\n  'default.currency': '默认币种',\n  'convertCurrency': '折合'\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/book.js",
    "content": "export default {\n  'book': '账本',\n\n  'book.name': '账本名',\n  'book.group': '所属组',\n  'book.description.eable': '是否展示描述',\n  'book.time.eable': '是否展示时间',\n  'book.image.eable': '是否支持图片',\n  'book.default.expense.account': '默认支出账户',\n  'book.default.income.account': '默认收入账户',\n  'book.default.transfer.from.account': '默认转出账户',\n  'book.default.transfer.to.account': '默认转入账户',\n  'book.default.expense.category': '默认支出类别',\n  'book.default.income.category': '默认收入类别',\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/category.js",
    "content": "export default {\n  'expense.category': '支出分类',\n  'income.category': '收入分类',\n  'tag': '交易标签',\n  'payee': '交易对象',\n  'parent.category': '所属类别',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/common.js",
    "content": "export default {\n  'app.title': '记账管理系统',\n\n  'yes': '是',\n  'no': '否',\n  'null': '空',\n\n  'add': '添加',\n  'new': '新增',\n  'copy': '复制',\n  'search': '查询',\n  'update': '修改',\n  'delete': '删除',\n  'config': '设置',\n  'reload': '刷新',\n\n  'form.cancel': '取消',\n  'form.reset': '重置',\n  'form.submit': '提交',\n\n  'today': '今天',\n  'this.week': '本周',\n  'this.month': '本月',\n  'this.year': '今年',\n  'last.year': '去年',\n  'in.7.days': '7天内',\n  'in.30.days': '30天内',\n  'in.1.year': '1年内',\n\n  'operation.success': '操作成功',\n  'delete.confirm': '删除之后无法恢复，确定删除{name}吗？',\n\n  'name': '名称',\n  'is.enable': '是否可用',\n  'notes': '备注',\n  'operation': '操作',\n\n  'pagination.showTotal': '第 {range0}-{range1} 条 / 总共 {total} 条',\n\n  'unit.days': '天',\n  'unit.weeks': '周',\n  'unit.months': '月',\n  'unit.quarters': '季度',\n  'unit.years': '年',\n\n  'confirm.operation': '状态将更改为确认，确定此操作吗？',\n\n  'more': '更多'\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/flow.js",
    "content": "export default {\n\n  'expense': '支出',\n  'income': '收入',\n  'transfer': '转账',\n  'transfer.from': '转出',\n  'transfer.to': '转入',\n  'adjust.balance': '余额调整',\n\n  'expenseable': '可支出',\n  'incomeable': '可收入',\n  'transferable': '可转账',\n  'transferFromAble': '可转出',\n  'transferToAble': '可转入',\n\n  'delete.confirm.balance': '删除账单会撤回对应账户余额变动且无法恢复，确定删除吗？',\n\n  'description': '描述',\n  'amount': '金额',\n  'category': '类别',\n  'flow.book': '所属账本',\n  'flow.tag': '标签',\n  'flow.payee': '付款对象',\n  'flow.createTime': '时间',\n  'flow.status': '状态',\n  'flow.type': '交易类型',\n  'flow.image': '账单图片',\n  'image': '图片',\n  'flow.isConfirmed': '是否确认',\n  'transfer.from.account': '转出账户',\n  'transfer.to.account': '转入账户',\n\n  'update.tag.amount': '修改标签金额',\n  'tag.amount': '标签金额',\n\n  'confirm': '确认',\n  'refund': '退款',\n\n  'amount.between': '金额范围',\n  'time.between': '时间范围',\n\n  'flow.status1': '正常',\n  'flow.status2': '未确认',\n  'flow.status3': '已退款',\n\n  'flow.record': '流水记录',\n\n  'upload.size.error': '单个文件不能超过1.5MB',\n  'delete.tooltip': '已退款的账单必须先删除对应的退款记录。'\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/footer.js",
    "content": "export default {\n  'footer.download.ios': '下载iOS',\n  'footer.download.andriod': '下载安卓',\n  'footer.download.weibo': '关注微博',\n  'footer.download.zhihu': '关注知乎',\n  'footer.download.contact': '联系我们',\n  'footer.download.about': '关于我们',\n  'company.name': '武汉九快科技有限公司',\n  'footer.no': '鄂ICP备2021015799号-1',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/group.js",
    "content": "export default {\n  'group': '组',\n  'group.name': '组名',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/menu.js",
    "content": "export default {\n  'menu.report': '报表',\n  'menu.overview': '概览',\n  'menu.expense.category.reports': '支出分类',\n  'menu.expense.tag.reports': '支出标签',\n  'menu.income.category.reports': '收入分类',\n  'menu.income.tag.reports': '收入标签',\n  'menu.balance.sheet': '资产负债表',\n  'menu.flow.trend': '收支趋势图',\n  'menu.asset.trend': '资产趋势图',\n  'menu.account.list': '账户列表',\n  'menu.checking.account': '活期账户',\n  'menu.credit.account': '信用账户',\n  'menu.debt.account': '贷款账户',\n  'menu.asset.account': '资产账户',\n  'menu.account.overview': '账户管理',\n  'menu.bookkeeping': '记账',\n  'menu.expense': '支出',\n  'menu.income': '收入',\n  'menu.transfer': '转账',\n  'menu.flow': '流水',\n  'menu.audit': '对账',\n  'menu.settings': '设置',\n  'menu.category': '分类管理',\n  'menu.book': '账本管理',\n  'menu.group': '组管理',\n  'menu.security': '安全设置',\n  'menu.budget': '预算设置',\n  'menu.schedule': '预定设置',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/report.js",
    "content": "export default {\n  'asset': '资产',\n  'debt': '负债',\n  'net.worth': '净资产',\n\n  'income.expense.table': '收支表',\n  'frequency.expense': '支出次数',\n  'frequency.income': '收入次数',\n\n  'expense.trend': '支出趋势',\n  'expense.tag': '支出标签',\n  'income.trend': '收入趋势',\n  'income.tag': '收入标签',\n\n  'gross.amount': '总金额',\n  'gross.balance': '总余额',\n  'gross.limit': '总额度',\n  'gross.remain.limit': '总剩余额度',\n  'gross.expense': '总支出',\n  'gross.income': '总收入',\n  'gross.surplus': '总结余',\n  'gross.transfer.from': '总转出',\n  'gross.transfer.to': '总转入',\n\n  'report.break.down.label': '划分单位',\n  'report.expense.condition': '支出条件',\n  'report.income.condition': '收入条件',\n\n  'report.asset.category': '资产分类',\n  'report.debt.category': '负债分类',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/rules.js",
    "content": "export default {\n  'rules.required': '必填项!',\n\n  'rules.userName.required': '请输入用户名!',\n  'rules.userName.pattern': '请输入1-16位数字或字母。',\n\n  'rules.password.required': '请输入密码!',\n  'rules.password.pattern': '请输入6-32位数字，字母或特殊字符。',\n\n  'rules.email.pattern': '请输入正确的Email。',\n\n  'rules.name.required': '请输入名称!',\n  'rules.name.pattern': '长度不能超过16个字符',\n\n  'rules.balance.required': '请输入余额!',\n  'rules.balance.pattern': '余额为数字且不能超过9位数',\n\n  'rules.amount.required': '请输入金额',\n  'rules.amount.pattern': '金额为数字且不能超过9位数',\n\n  'rules.description.pattern': '长度不能超过16个字符',\n\n  'rules.notes.pattern': '长度不能超过1000个字符',\n\n  'rules.time.required': '请输入时间！',\n\n  'rules.category.required': '请选择类别',\n\n  'rules.account.required': '请选择账户',\n\n  'rules.limit.required': '请输入信用额度',\n  'rules.limit.pattern': '余额为数字且不能超过9位数',\n\n  'rules.apr.pattern': '年化利率不超过三位数',\n\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/schedule.js",
    "content": "export default {\n  'schedule.expense.schedule': '支出预定',\n  'schedule.income.schedule': '收入预定',\n  'schedule.transfer.schedule': '转账预定',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN/user.js",
    "content": "export default {\n\n  'signin': '登录',\n  'signin.success': '登录成功！',\n  'placeholder.userName': '1-16位数字或字母',\n  'placeholder.password': '6-32位数字，字母或特殊字符',\n  'signin.keep': '保存登录状态',\n  'signin.forget': '忘记密码',\n\n  'register': '注册账户',\n  'register.success': '注册成功！',\n  'placeholder.invite.code': '邀请码',\n  'placeholder.email': '邮箱可用于找回密码，选填。',\n  'register.signin': '已有账号，登录',\n\n  'profile': '个人中心',\n  'settings': '个人设置',\n  'logout': '退出登录',\n  'update.password': '修改密码',\n  'old.password': '原始密码',\n  'new.password': '新密码',\n\n  'set.default': '设为默认',\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/locales/zh-CN.js",
    "content": "import common from './zh-CN/common';\nimport menu from './zh-CN/menu';\nimport footer from './zh-CN/footer';\nimport user from './zh-CN/user';\nimport account from './zh-CN/account';\nimport flow from './zh-CN/flow';\nimport category from './zh-CN/category';\nimport rules from './zh-CN/rules';\nimport report from './zh-CN/report';\nimport group from './zh-CN/group';\nimport book from './zh-CN/book';\nimport schedule from './zh-CN/schedule';\n\nexport default {\n  ...common,\n  ...menu,\n  ...footer,\n  ...user,\n  ...account,\n  ...flow,\n  ...category,\n  ...rules,\n  ...report,\n  ...group,\n  ...book,\n  ...schedule\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/account.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getEnable, getExpenseable, getIncomeable, getTransferFromAble, getTransferToAble } from '@/services/account';\n\nexport default modelExtend(model, {\n  namespace: 'account',\n  state: {\n    getEnableResponse: undefined,\n    getExpenseableResponse: undefined,\n    getIncomeableResponse: undefined,\n    getTransferFromAbleResponse: undefined,\n    getTransferToAbleResponse: undefined,\n  },\n  effects: {\n    *fetchEnable(_, { call, put }) {\n      const response = yield call(getEnable);\n      yield put({\n        type: 'updateState',\n        payload: { getEnableResponse: response },\n      });\n    },\n    *fetchExpenseable(_, { call, put }) {\n      const response = yield call(getExpenseable);\n      yield put({\n        type: 'updateState',\n        payload: { getExpenseableResponse: response },\n      });\n    },\n    *fetchIncomeable(_, { call, put }) {\n      const response = yield call(getIncomeable);\n      yield put({\n        type: 'updateState',\n        payload: { getIncomeableResponse: response },\n      });\n    },\n    *fetchTransferFromAble(_, { call, put }) {\n      const response = yield call(getTransferFromAble);\n      yield put({\n        type: 'updateState',\n        payload: { getTransferFromAbleResponse: response },\n      });\n    },\n    *fetchTransferToAble(_, { call, put }) {\n      const response = yield call(getTransferToAble);\n      yield put({\n        type: 'updateState',\n        payload: { getTransferToAbleResponse: response },\n      });\n    },\n    *refresh(_, { __, put }) {\n      yield put({ type: 'fetchEnable' });\n      //if (payload.expenseable) {\n        yield put({ type: 'fetchExpenseable' });\n      //}\n      //if (payload.incomeable) {\n        yield put({ type: 'fetchIncomeable' });\n      //}\n      //if (payload.transferFromAble) {\n        yield put({ type: 'fetchTransferFromAble' });\n      //}\n      //if (payload.transferToAble) {\n        yield put({ type: 'fetchTransferToAble' });\n      //}\n    },\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/currency.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getAll } from '@/services/currency';\n\nexport default modelExtend(model, {\n  namespace: 'currency',\n  state: {\n    getAllResponse: undefined,\n  },\n  effects: {\n    *fetchAll(_, { call, put }) {\n      const response = yield call(getAll);\n      yield put({\n        type: 'updateState',\n        payload: { getAllResponse: response },\n      });\n    }\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/expenseCategory.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { querySimple } from '@/services/expense-category';\n\nexport default modelExtend(model, {\n  namespace: 'expenseCategory',\n  state: {\n    querySimpleResponse: undefined,\n  },\n  effects: {\n    *fetchSimple({ payload }, { call, put }) {\n      const response = yield call(querySimple, payload);\n      yield put({\n        type: 'updateState',\n        payload: { querySimpleResponse: response },\n      });\n    },\n    *refresh(_, { __, put }) {\n      yield put({ type: 'fetchSimple' });\n    }\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/flow.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport {getUploadToken} from '@/services/flow-image';\nimport {getImages} from '@/services/flow';\n\nexport default modelExtend(model, {\n  namespace: 'flow',\n  state: {\n    uploadToken: undefined,\n    images: undefined,\n  },\n  effects: {\n    *fetchUploadToken(_, { call, put }) {\n      const response = yield call(getUploadToken);\n      if (response && response.success && response.data) {\n        yield put({\n          type: 'updateState',\n          payload: {\n            uploadToken: response.data,\n          },\n        });\n      }\n    },\n    *fetchImages({ payload }, { call, put }) {\n      const response = yield call(getImages, payload.id);\n      if (response && response.success && response.data) {\n        yield put({\n          type: 'updateState',\n          payload: {\n            images: response.data,\n          },\n        });\n      }\n    },\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/incomeCategory.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { querySimple } from '@/services/income-category';\n\nexport default modelExtend(model, {\n  namespace: 'incomeCategory',\n  state: {\n    querySimpleResponse: undefined,\n  },\n  effects: {\n    *fetchSimple({ payload }, { call, put }) {\n      const response = yield call(querySimple, payload);\n      yield put({\n        type: 'updateState',\n        payload: { querySimpleResponse: response },\n      });\n    },\n    *refresh(_, { __, put }) {\n      yield put({ type: 'fetchSimple' });\n    }\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/modal.js",
    "content": "export default {\n  namespace: 'modal',\n  state: {\n    component: null,\n    visible: false,\n    currentItem: { },\n    type: 1, // 1是新增，2是更新，3是复制, 4是退款\n  },\n  reducers: {\n    show(state, { payload }) {\n      return { ...state, ...payload, visible: true }\n    },\n    hide(state) {\n      return { ...state, visible: false, currentItem: { } }\n    },\n  },\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/payee.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getEnable, getExpenseable, getIncomeable } from '@/services/payee';\n\nexport default modelExtend(model, {\n  namespace: 'payee',\n  state: {\n    getEnableResponse: undefined,\n    getExpenseableResponse: undefined,\n    getIncomeableResponse: undefined,\n  },\n  effects: {\n    *fetchEnable(_, { call, put }) {\n      const response = yield call(getEnable);\n      yield put({\n        type: 'updateState',\n        payload: { getEnableResponse: response },\n      });\n    },\n    *fetchExpenseable(_, { call, put }) {\n      const response = yield call(getExpenseable);\n      yield put({\n        type: 'updateState',\n        payload: { getExpenseableResponse: response },\n      });\n    },\n    *fetchIncomeable(_, { call, put }) {\n      const response = yield call(getIncomeable);\n      yield put({\n        type: 'updateState',\n        payload: { getIncomeableResponse: response },\n      });\n    },\n    *refresh({ payload }, { _, put }) {\n      yield put({ type: 'fetchEnable' });\n      //if (payload.expenseable) {\n        yield put({ type: 'fetchExpenseable' });\n      //}\n      //if (payload.incomeable) {\n        yield put({ type: 'fetchIncomeable' });\n      //}\n    }\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/session.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport {getSessionUser} from '@/services/user';\n\nexport default modelExtend(model, {\n  namespace: 'session',\n  state: {\n    user: undefined,\n    defaultBook: undefined,\n    defaultGroup: undefined,\n  },\n  effects: {\n    *fetchSession(_, { call, put }) {\n      const response = yield call(getSessionUser);\n      if (response && response.success && response.data) {\n        yield put({\n          type: 'updateState',\n          payload: {\n            user: response.data.userSessionVO,\n            defaultBook: response.data.defaultBook,\n            defaultGroup: response.data.defaultGroup,\n          },\n        });\n      }\n    },\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/models/tag.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getEnable, getExpenseable, getIncomeable, getTransferable } from '@/services/tag';\n\nexport default modelExtend(model, {\n  namespace: 'tag',\n  state: {\n    getEnableResponse: undefined,\n    getExpenseableResponse: undefined,\n    getIncomeableResponse: undefined,\n    getTransferableResponse: undefined,\n  },\n  effects: {\n    *fetchEnable(_, { call, put }) {\n      const response = yield call(getEnable);\n      yield put({\n        type: 'updateState',\n        payload: { getEnableResponse: response },\n      });\n    },\n    *fetchExpenseable(_, { call, put }) {\n      const response = yield call(getExpenseable);\n      yield put({\n        type: 'updateState',\n        payload: { getExpenseableResponse: response },\n      });\n    },\n    *fetchIncomeable(_, { call, put }) {\n      const response = yield call(getIncomeable);\n      yield put({\n        type: 'updateState',\n        payload: { getIncomeableResponse: response },\n      });\n    },\n    *fetchTransferable(_, { call, put }) {\n      const response = yield call(getTransferable);\n      yield put({\n        type: 'updateState',\n        payload: { getTransferableResponse: response },\n      });\n    },\n    *refresh({ payload }, { _, put }) {\n      yield put({ type: 'fetchEnable' });\n      //if (payload.expenseable) {\n        yield put({ type: 'fetchExpenseable' });\n     // }\n      //if (payload.incomeable) {\n        yield put({ type: 'fetchIncomeable' });\n      //}\n      //if (payload.transferable) {\n        yield put({ type: 'fetchTransferable' });\n      //}\n    }\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/AssetAccountModal.jsx",
    "content": "import {useDispatch, useSelector} from \"umi\";\nimport { useState, useEffect } from 'react';\nimport {Form, Input, Switch, Col, Row, Select} from 'antd';\nimport FormModal from \"@/components/FormModal\";\nimport {nameRules, notesRules, balanceRequiredRules, requiredRules} from '@/utils/rules';\nimport { formProp }  from '@/utils/var';\nimport {getNull, tableChangeQueryFormat} from '@/utils/util';\nimport { create } from '@/services/asset-account';\nimport { update } from '@/services/account';\nimport {useCurrencyResponseSelectData} from \"@/utils/hooks\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const {\n    assetAccountPagination : pagination,\n    assetAccountSorter : sorter\n  } = useSelector(state => state.accounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  const { getAllResponse : currencyResponse } = useSelector(state => state.currency);\n  const [currencyList] = useCurrencyResponseSelectData(currencyResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!currencyResponse) dispatch({ type: 'currency/fetchAll' });\n      if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({});\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        include: true,\n        expenseable: false,\n        incomeable: false,\n        transferToAble: true,\n        transferFromAble: true,\n        currencyCode: defaultGroup.defaultCurrencyCode\n      });\n    } else {\n      // 数字类型的校验存在问题, antd bug\n      currentItem.balance = currentItem.balance.toString();\n      setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n    }\n  }, [visible, type, currentItem]);\n\n  function successHandler(response) {\n    dispatch({ type: 'accounts/queryAssetAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'account/refresh', payload: { ...response.data } });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('asset.account')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form {...formProp} form={form}>\n        <Form.Item label={t('currency')} name=\"currencyCode\" rules={requiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Select options={currencyList} showArrow showSearch filterOption optionFilterProp={\"label\"} disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()} labelCol={{ style: { width: 68 } }}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('account.current.balance')} name=\"balance\" rules={balanceRequiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Input disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('account.card.no')} name=\"no\" labelCol={{ style: { width: 68 } }}>\n          <Input />\n        </Form.Item>\n        <Row gutter={8}>\n          <Col span={6}>\n            <Form.Item label={t('expenseable')} valuePropName=\"checked\" name=\"expenseable\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('incomeable')} valuePropName=\"checked\" name=\"incomeable\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('transferToAble')} valuePropName=\"checked\" name=\"transferToAble\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('transferFromAble')} valuePropName=\"checked\" name=\"transferFromAble\">\n              <Switch />\n            </Form.Item>\n          </Col>\n        </Row>\n        <Form.Item label={t('account.include')} valuePropName=\"checked\" name=\"include\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/AssetAccountTable.jsx",
    "content": "import { useEffect } from 'react';\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport {Button, Descriptions, message, Modal, Space, Switch, Table} from \"antd\";\nimport { usePaginationAndData } from \"@/utils/hooks\";\nimport {\n  remove,\n  toggle,\n  toggleExpenseable,\n  toggleInclude,\n  toggleIncomeable,\n  toggleTransferFromAble, toggleTransferToAble\n} from '@/services/account';\nimport {getTimeFormat, tableChangeQueryFormat} from \"@/utils/util\";\nimport {showFlowModal} from '@/utils/flow';\nimport {\n  balanceCol,\n  accountIncludeCol,\n  accountExpenseableCol,\n  accountIncomeableCol,\n  accountTransferToCol,\n  accountTransferFromCol,\n  accountEnableCol\n} from '@/utils/columns';\nimport {tableProp} from \"@/utils/var\";\nimport moment from \"moment\";\nimport AssetAccountModal from \"@/pages/accounts/AssetAccountModal\";\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'accounts/queryAssetAccount' });\n  }, []);\n\n  const { queryAssetAccountResponse: queryResponse } = useSelector(state => state.accounts);\n  const queryLoading = useSelector(state => state.loading.effects['accounts/queryAssetAccount']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n  const { assetAccountPagination : pagination, assetAccountSorter : sorter } = useSelector(state => state.accounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n  function tableChangeHandler(pagination, _, sorter) {\n    dispatch({ type: 'accounts/updateState', payload: {assetAccountPagination: pagination, assetAccountSorter: sorter} });\n    dispatch({ type: 'accounts/queryAssetAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n  }\n\n  function refresh() {\n    dispatch({ type: 'accounts/queryAssetAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'account/refresh' });\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const toggleHandler = async (record) => {\n    const response = await toggle(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleIncludeHandler = async (record) => {\n    const response = await toggleInclude(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleExpenseableHandler = async (record) => {\n    const response = await toggleExpenseable(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleIncomeableHandler = async (record) => {\n    const response = await toggleIncomeable(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleTransferFromAbleHandler = async (record) => {\n    const response = await toggleTransferFromAble(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleTransferToAbleHandler = async (record) => {\n    const response = await toggleTransferToAble(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n\n  const updateHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: AssetAccountModal, type: 2, currentItem: record }});\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refresh();\n        }\n      }\n    });\n  }\n\n  const adjustBalanceHandler = (record) => {\n    showFlowModal(4, 2, {...record});\n  }\n\n  const columns = [\n    {\n      title: t('name'),\n      dataIndex: 'name',\n    },\n    balanceCol(),\n    {\n      title: t('currency'),\n      dataIndex: 'currencyCode',\n      sorter: true,\n      width: 70,\n    },\n    {\n      title: t('convertCurrency') + (defaultGroup ? defaultGroup.defaultCurrencyCode : ''),\n      dataIndex: 'convertedBalance',\n      width: 80,\n    },\n    {\n      title: t('account.asOfDate'),\n      dataIndex: 'asOfDate',\n      sorter: true,\n      width: 90,\n      render: value => moment(value).format('YYYY-MM-DD')\n    },\n    accountIncludeCol(toggleIncludeHandler),\n    accountExpenseableCol(toggleExpenseableHandler),\n    accountIncomeableCol(toggleIncomeableHandler),\n    accountTransferToCol(toggleTransferToAbleHandler),\n    accountTransferFromCol(toggleTransferFromAbleHandler),\n    accountEnableCol(toggleHandler),\n    {\n      title: t('operation'),\n      key: 'operation',\n      align: 'center',\n      width: 150,\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={() => updateHandler(record)}>{t('update')}</Button>\n            <Button size=\"small\" onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n            <Button size=\"small\" onClick={() => adjustBalanceHandler(record)}>{t('adjust.balance')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      size=\"small\"\n      columns={columns}\n      expandable={{\n        expandedRowRender: record => <Descriptions size=\"small\" bordered column={3}>\n          <Descriptions.Item label={t('account.card.no')}>{record.no ? record.no : t('null')}</Descriptions.Item>\n          <Descriptions.Item label={t('account.initial.balance')}>{record.initialBalance}</Descriptions.Item>\n          <Descriptions.Item label={t('notes')}>{record.notes ? record.notes : t('null')}</Descriptions.Item>\n        </Descriptions>,\n      }}\n      dataSource={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      loading={queryLoading}\n      onChange={tableChangeHandler}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/CheckingAccountModal.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport {Form, Input, Switch, Col, Row, Select} from 'antd';\nimport {useDispatch, useSelector} from \"umi\";\nimport FormModal from \"@/components/FormModal\";\nimport {nameRules, notesRules, balanceRequiredRules, requiredRules} from '@/utils/rules';\nimport { formProp }  from '@/utils/var';\nimport {getNull, tableChangeQueryFormat} from '@/utils/util';\nimport {useCurrencyResponseSelectData} from \"@/utils/hooks\";\nimport { create } from '@/services/checking-account';\nimport { update } from '@/services/account';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const {\n    checkingAccountPagination : pagination,\n    checkingAccountSorter : sorter\n  } = useSelector(state => state.accounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  const { getAllResponse : currencyResponse } = useSelector(state => state.currency);\n  const [currencyList] = useCurrencyResponseSelectData(currencyResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!currencyResponse) dispatch({ type: 'currency/fetchAll' });\n      if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({});\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        ...{\n          include: true,\n          expenseable: true,\n          incomeable: true,\n          transferToAble: true,\n          transferFromAble: true,\n          currencyCode: defaultGroup.defaultCurrencyCode\n        }\n      });\n    } else {\n      // 数字类型的校验存在问题, antd bug\n      currentItem.balance = currentItem.balance + '';\n      setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n    }\n  }, [visible, type, currentItem]);\n\n  function successHandler(response) {\n    dispatch({ type: 'accounts/queryCheckingAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'account/refresh', payload: { ...response.data } });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('checking.account')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form {...formProp} form={form}>\n        <Form.Item label={t('currency')} name=\"currencyCode\" rules={requiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Select options={currencyList} showArrow showSearch filterOption optionFilterProp={\"label\"} disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()} labelCol={{ style: { width: 68 } }}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('account.current.balance')} name=\"balance\" rules={balanceRequiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Input disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('account.card.no')} name=\"no\" labelCol={{ style: { width: 68 } }}>\n          <Input />\n        </Form.Item>\n        <Row gutter={8}>\n          <Col span={6}>\n            <Form.Item label={t('expenseable')} valuePropName=\"checked\" name=\"expenseable\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('incomeable')} valuePropName=\"checked\" name=\"incomeable\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('transferToAble')} valuePropName=\"checked\" name=\"transferToAble\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('transferFromAble')} valuePropName=\"checked\" name=\"transferFromAble\">\n              <Switch />\n            </Form.Item>\n          </Col>\n        </Row>\n        <Form.Item label={t('account.include')} valuePropName=\"checked\" name=\"include\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()} labelCol={{ style: { width: 68 } }}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/CheckingAccountTable.jsx",
    "content": "import { useEffect } from 'react';\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport {Button, Descriptions, message, Modal, Space, Table} from \"antd\";\nimport { usePaginationAndData } from \"@/utils/hooks\";\nimport { remove, toggle, toggleInclude, toggleExpenseable, toggleIncomeable, toggleTransferFromAble, toggleTransferToAble } from '@/services/account';\nimport { tableChangeQueryFormat } from \"@/utils/util\";\nimport {showFlowModal} from '@/utils/flow';\nimport CheckingAccountModal from \"./CheckingAccountModal\";\nimport t from \"@/utils/translate\";\nimport {\n  accountEnableCol,\n  accountExpenseableCol,\n  accountIncludeCol,\n  accountIncomeableCol, accountTransferFromCol,\n  accountTransferToCol,\n  balanceCol\n} from \"@/utils/columns\";\nimport {tableProp} from \"@/utils/var\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'accounts/queryCheckingAccount' });\n  }, []);\n\n  const { queryCheckingAccountResponse: queryResponse } = useSelector(state => state.accounts);\n  const queryLoading = useSelector(state => state.loading.effects['accounts/queryCheckingAccount']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n  const { checkingAccountPagination : pagination, checkingAccountSorter : sorter } = useSelector(state => state.accounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n  function tableChangeHandler(pagination, _, sorter) {\n    dispatch({ type: 'accounts/updateState', payload: {checkingAccountPagination: pagination, checkingAccountSorter: sorter} });\n    dispatch({ type: 'accounts/queryCheckingAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n  }\n\n  function refresh() {\n    dispatch({ type: 'accounts/queryCheckingAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'account/refresh'});\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const toggleHandler = async (record) => {\n    const response = await toggle(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleIncludeHandler = async (record) => {\n    const response = await toggleInclude(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleExpenseableHandler = async (record) => {\n    const response = await toggleExpenseable(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleIncomeableHandler = async (record) => {\n    const response = await toggleIncomeable(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleTransferFromAbleHandler = async (record) => {\n    const response = await toggleTransferFromAble(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleTransferToAbleHandler = async (record) => {\n    const response = await toggleTransferToAble(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n\n  const updateHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: CheckingAccountModal, type: 2, currentItem: record }});\n  }\n\n  const adjustBalanceHandler = (record) => {\n    showFlowModal(4, 2, {...record});\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refresh();\n        }\n      }\n    });\n  }\n\n  const columns = [\n    {\n      title: t('name'),\n      dataIndex: 'name',\n    },\n    balanceCol(),\n    {\n      title: t('currency'),\n      dataIndex: 'currencyCode',\n      sorter: true,\n      width: 70,\n    },\n    {\n      title: t('convertCurrency') + (defaultGroup ? defaultGroup.defaultCurrencyCode : ''),\n      dataIndex: 'convertedBalance',\n      width: 80,\n    },\n    accountIncludeCol(toggleIncludeHandler),\n    accountExpenseableCol(toggleExpenseableHandler),\n    accountIncomeableCol(toggleIncomeableHandler),\n    accountTransferToCol(toggleTransferToAbleHandler),\n    accountTransferFromCol(toggleTransferFromAbleHandler),\n    accountEnableCol(toggleHandler),\n    {\n      title: t('operation'),\n      key: 'operation',\n      align: 'center',\n      width: 170,\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={() => updateHandler(record)}>{t('update')}</Button>\n            <Button size=\"small\" onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n            <Button size=\"small\" onClick={() => adjustBalanceHandler(record)}>{t('adjust.balance')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      size=\"small\"\n      columns={columns}\n      expandable={{\n        expandedRowRender: record => <Descriptions size=\"small\" bordered column={3}>\n          <Descriptions.Item label={t('account.card.no')}>{record.no ? record.no : t('null')}</Descriptions.Item>\n          <Descriptions.Item label={t('account.initial.balance')}>{record.initialBalance}</Descriptions.Item>\n          <Descriptions.Item label={t('notes')}>{record.notes ? record.notes : t('null')}</Descriptions.Item>\n        </Descriptions>,\n      }}\n      dataSource={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      loading={queryLoading}\n      onChange={tableChangeHandler}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/CreditAccountModal.jsx",
    "content": "import {useDispatch, useSelector} from \"umi\";\nimport { useState, useEffect } from 'react';\nimport {Form, Input, Switch, Col, Row, Select} from 'antd';\nimport {nameRules, notesRules, balanceRequiredRules, limitRequiredRules, requiredRules} from '@/utils/rules';\nimport { formProp }  from '@/utils/var';\nimport { getNull, tableChangeQueryFormat }  from '@/utils/util';\nimport { create } from '@/services/credit-account';\nimport { update } from '@/services/account';\nimport FormModal from \"@/components/FormModal\";\nimport t from \"@/utils/translate\";\nimport {useCurrencyResponseSelectData} from \"@/utils/hooks\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const {\n    creditAccountPagination : pagination,\n    creditAccountSorter : sorter\n  } = useSelector(state => state.accounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  const { getAllResponse : currencyResponse } = useSelector(state => state.currency);\n  const [currencyList] = useCurrencyResponseSelectData(currencyResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!currencyResponse) dispatch({ type: 'currency/fetchAll' });\n      if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({});\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        include: true,\n        expenseable: true,\n        incomeable: false,\n        transferToAble: true,\n        transferFromAble: false,\n        currencyCode: defaultGroup.defaultCurrencyCode\n      });\n    } else {\n      // 数字类型的校验存在问题, antd bug\n      currentItem.balance = currentItem.balance.toString();\n      currentItem.limit = currentItem.limit.toString();\n      setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n    }\n  }, [visible, type, currentItem]);\n\n  function successHandler(response) {\n    dispatch({ type: 'accounts/queryCreditAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'account/refresh', payload: { ...response.data } });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('credit.account')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form {...formProp} form={form}>\n        <Form.Item label={t('currency')} name=\"currencyCode\" rules={requiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Select options={currencyList} showArrow showSearch filterOption optionFilterProp={\"label\"} disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()} labelCol={{ style: { width: 68 } }}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('account.current.balance')} name=\"balance\" rules={balanceRequiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Input disabled={type === 2} />\n        </Form.Item>\n        <Row gutter={8}>\n          <Col span={12}>\n            <Form.Item label={t('credit.limit')} name=\"limit\" rules={limitRequiredRules()} labelCol={{ style: { width: 68 } }}>\n              <Input />\n            </Form.Item>\n          </Col>\n          <Col span={12}>\n            <Form.Item label={t('account.billDay')} name=\"billDay\">\n              <Select allowClear={true} showSearch>\n                {Array(31).fill().map((_, i) => <Select.Option key={i} value={i+1}>{i+1}</Select.Option>)}\n              </Select>\n            </Form.Item>\n          </Col>\n        </Row>\n        <Form.Item label={t('account.card.no')} name=\"no\" labelCol={{ style: { width: 68 } }}>\n          <Input />\n        </Form.Item>\n        <Row gutter={8}>\n          <Col span={6}>\n            <Form.Item label={t('expenseable')} valuePropName=\"checked\" name=\"expenseable\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('incomeable')} valuePropName=\"checked\" name=\"incomeable\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('transferToAble')} valuePropName=\"checked\" name=\"transferToAble\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('transferFromAble')} valuePropName=\"checked\" name=\"transferFromAble\">\n              <Switch />\n            </Form.Item>\n          </Col>\n        </Row>\n        <Form.Item label={t('account.include')} valuePropName=\"checked\" name=\"include\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/CreditAccountTable.jsx",
    "content": "import { useEffect } from 'react';\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport {Button, Descriptions, message, Modal, Space, Table} from \"antd\";\nimport { usePaginationAndData } from \"@/utils/hooks\";\nimport {\n  remove,\n  toggle,\n  toggleExpenseable,\n  toggleInclude,\n  toggleIncomeable,\n  toggleTransferFromAble, toggleTransferToAble\n} from '@/services/account';\nimport { tableChangeQueryFormat } from \"@/utils/util\";\nimport {showFlowModal} from '@/utils/flow';\nimport {\n  balanceCol,\n  accountIncludeCol,\n  accountExpenseableCol,\n  accountIncomeableCol,\n  accountTransferToCol,\n  accountTransferFromCol,\n  accountEnableCol,\n} from '@/utils/columns';\nimport {tableProp} from \"@/utils/var\";\nimport t from \"@/utils/translate\";\nimport CreditAccountModal from \"@/pages/accounts/CreditAccountModal\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'accounts/queryCreditAccount' });\n  }, []);\n\n  const { queryCreditAccountResponse: queryResponse } = useSelector(state => state.accounts);\n  const queryLoading = useSelector(state => state.loading.effects['accounts/queryCreditAccount']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n  const { creditAccountPagination : pagination, creditAccountSorter : sorter } = useSelector(state => state.accounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n  function tableChangeHandler(pagination, _, sorter) {\n    dispatch({ type: 'accounts/updateState', payload: {creditAccountPagination: pagination, creditAccountSorter: sorter} });\n    dispatch({ type: 'accounts/queryCreditAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n  }\n\n  function refresh() {\n    dispatch({ type: 'accounts/queryCreditAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'account/refresh' });\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const toggleHandler = async (record) => {\n    const response = await toggle(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleIncludeHandler = async (record) => {\n    const response = await toggleInclude(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleExpenseableHandler = async (record) => {\n    const response = await toggleExpenseable(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleIncomeableHandler = async (record) => {\n    const response = await toggleIncomeable(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleTransferFromAbleHandler = async (record) => {\n    const response = await toggleTransferFromAble(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleTransferToAbleHandler = async (record) => {\n    const response = await toggleTransferToAble(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n\n  const updateHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: CreditAccountModal, type: 2, currentItem: record }});\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refresh();\n        }\n      }\n    });\n  }\n\n  const adjustBalanceHandler = (record) => {\n    showFlowModal(4, 2, {...record});\n  }\n\n  const columns = [\n    {\n      title: t('name'),\n      dataIndex: 'name',\n    },\n    balanceCol(),\n    {\n      title: t('currency'),\n      dataIndex: 'currencyCode',\n      sorter: true,\n      width: 70,\n    },\n    {\n      title: t('convertCurrency') + (defaultGroup ? defaultGroup.defaultCurrencyCode : ''),\n      dataIndex: 'convertedBalance',\n      width: 80,\n    },\n    {\n      title: t('credit.limit'),\n      dataIndex: 'limit',\n      sorter: true,\n      width: 85,\n    },\n    {\n      title: t('remain.limit'),\n      dataIndex: 'remainLimit',\n      sorter: true,\n      width: 85,\n    },\n    accountIncludeCol(toggleIncludeHandler),\n    accountExpenseableCol(toggleExpenseableHandler),\n    accountIncomeableCol(toggleIncomeableHandler),\n    accountTransferToCol(toggleTransferToAbleHandler),\n    accountTransferFromCol(toggleTransferFromAbleHandler),\n    accountEnableCol(toggleHandler),\n    {\n      title: t('operation'),\n      key: 'operation',\n      align: 'center',\n      width: 150,\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={() => updateHandler(record)}>{t('update')}</Button>\n            <Button size=\"small\" onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n            <Button size=\"small\" onClick={() => adjustBalanceHandler(record)}>{t('adjust.balance')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      size=\"small\"\n      columns={columns}\n      expandable={{\n        expandedRowRender: record => <Descriptions size=\"small\" bordered column={6}>\n          <Descriptions.Item label={t('account.billDay')}>{record.billDay ? record.billDay : t('null')}</Descriptions.Item>\n          <Descriptions.Item label={t('account.card.no')}>{record.no ? record.no : t('null')}</Descriptions.Item>\n          <Descriptions.Item label={t('account.initial.balance')}>{record.initialBalance}</Descriptions.Item>\n          <Descriptions.Item label={t('notes')}>{record.notes ? record.notes : t('null')}</Descriptions.Item>\n        </Descriptions>,\n      }}\n      dataSource={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      loading={queryLoading}\n      onChange={tableChangeHandler}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/DebtAccountModal.jsx",
    "content": "import {useDispatch, useSelector} from \"umi\";\nimport { useState, useEffect } from 'react';\nimport {Form, Input, Switch, message, Col, Row, Select} from 'antd';\nimport {nameRules, notesRules, balanceRequiredRules, aprRules, requiredRules} from '@/utils/rules';\nimport { formProp }  from '@/utils/var';\nimport {getNull, validateForm, tableChangeQueryFormat} from '@/utils/util';\nimport { create } from '@/services/debt-account';\nimport { update } from '@/services/account';\nimport FormModal from \"@/components/FormModal\";\nimport t from \"@/utils/translate\";\nimport {useCurrencyResponseSelectData} from \"@/utils/hooks\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const {\n    debtAccountPagination : pagination,\n    debtAccountSorter : sorter\n  } = useSelector(state => state.accounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  const { getAllResponse : currencyResponse } = useSelector(state => state.currency);\n  const [currencyList] = useCurrencyResponseSelectData(currencyResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!currencyResponse) dispatch({ type: 'currency/fetchAll' });\n      if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({});\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        include: true,\n        expenseable: false,\n        incomeable: false,\n        transferToAble: true,\n        transferFromAble: true,\n        currencyCode: defaultGroup.defaultCurrencyCode\n      });\n    } else {\n      // 数字类型的校验存在问题, antd bug\n      currentItem.balance = currentItem.balance.toString();\n      if (currentItem.limit != null) currentItem.limit = currentItem.limit.toString();\n      if (currentItem.apr != null) currentItem.apr = currentItem.apr.toString();\n      setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n    }\n  }, [visible, type, currentItem]);\n\n  function successHandler(response) {\n    dispatch({ type: 'accounts/queryDebtAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'account/refresh', payload: { ...response.data } });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('debt.account')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form {...formProp} form={form}>\n        <Form.Item label={t('currency')} name=\"currencyCode\" rules={requiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Select options={currencyList} showArrow showSearch filterOption optionFilterProp={\"label\"} disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()} labelCol={{ style: { width: 68 } }}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('account.current.balance')} name=\"balance\" rules={balanceRequiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Input disabled={type === 2} />\n        </Form.Item>\n        <Row gutter={8}>\n          <Col span={12}>\n            <Form.Item label={t('debt.limit')} name=\"limit\" labelCol={{ style: { width: 68 } }}>\n              <Input />\n            </Form.Item>\n          </Col>\n          <Col span={12}>\n            <Form.Item label={t('account.apr')} name=\"apr\" rules={aprRules()}>\n              <Input />\n            </Form.Item>\n          </Col>\n        </Row>\n        <Form.Item label={t('account.card.no')} name=\"no\" labelCol={{ style: { width: 68 } }}>\n          <Input />\n        </Form.Item>\n        <Row gutter={8}>\n          <Col span={6}>\n            <Form.Item label={t('expenseable')} valuePropName=\"checked\" name=\"expenseable\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('incomeable')} valuePropName=\"checked\" name=\"incomeable\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('transferToAble')} valuePropName=\"checked\" name=\"transferToAble\">\n              <Switch />\n            </Form.Item>\n          </Col>\n          <Col span={6}>\n            <Form.Item label={t('transferFromAble')} valuePropName=\"checked\" name=\"transferFromAble\">\n              <Switch />\n            </Form.Item>\n          </Col>\n        </Row>\n        <Form.Item label={t('account.include')} valuePropName=\"checked\" name=\"include\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/DebtAccountTable.jsx",
    "content": "import { useEffect } from 'react';\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport {Button, Descriptions, message, Modal, Space, Switch, Table} from \"antd\";\nimport { usePaginationAndData } from \"@/utils/hooks\";\nimport {\n  remove,\n  toggle,\n  toggleExpenseable,\n  toggleInclude,\n  toggleIncomeable,\n  toggleTransferFromAble, toggleTransferToAble\n} from '@/services/account';\nimport { tableChangeQueryFormat } from \"@/utils/util\";\nimport {showFlowModal} from '@/utils/flow';\nimport {\n  balanceCol,\n  accountIncludeCol,\n  accountExpenseableCol,\n  accountIncomeableCol,\n  accountTransferToCol,\n  accountTransferFromCol,\n  accountEnableCol\n} from '@/utils/columns';\nimport FlagTag from \"@/components/FlagTag\";\nimport DebtAccountModal from \"./DebtAccountModal\";\nimport {tableProp} from \"@/utils/var\";\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'accounts/queryDebtAccount' });\n  }, []);\n\n  const { queryDebtAccountResponse : queryResponse } = useSelector(state => state.accounts);\n  const queryLoading = useSelector(state => state.loading.effects['accounts/queryDebtAccount']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n  const { debtAccountPagination : pagination, debtAccountSorter : sorter } = useSelector(state => state.accounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n  function tableChangeHandler(pagination, _, sorter) {\n    dispatch({ type: 'accounts/updateState', payload: {debtAccountPagination: pagination, debtAccountSorter: sorter} });\n    dispatch({ type: 'accounts/queryDebtAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n  }\n\n  function refresh() {\n    dispatch({ type: 'accounts/queryDebtAccount', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'account/refresh' });\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const toggleHandler = async (record) => {\n    const response = await toggle(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleIncludeHandler = async (record) => {\n    const response = await toggleInclude(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleExpenseableHandler = async (record) => {\n    const response = await toggleExpenseable(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleIncomeableHandler = async (record) => {\n    const response = await toggleIncomeable(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleTransferFromAbleHandler = async (record) => {\n    const response = await toggleTransferFromAble(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n  const toggleTransferToAbleHandler = async (record) => {\n    const response = await toggleTransferToAble(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n\n  const updateHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: DebtAccountModal, type: 2, currentItem: record }});\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refresh();\n        }\n      }\n    });\n  }\n\n  const adjustBalanceHandler = (record) => {\n    showFlowModal(4, 2, {...record});\n  }\n\n  const columns = [\n    {\n      title: t('name'),\n      dataIndex: 'name',\n    },\n    balanceCol(),\n    {\n      title: t('currency'),\n      dataIndex: 'currencyCode',\n      sorter: true,\n      width: 70,\n    },\n    {\n      title: t('convertCurrency') + (defaultGroup ? defaultGroup.defaultCurrencyCode : ''),\n      dataIndex: 'convertedBalance',\n      width: 80,\n    },\n    {\n      title: t('debt.limit'),\n      dataIndex: 'limit',\n      sorter: true,\n      width: 85,\n    },\n    {\n      title: t('remain.limit'),\n      dataIndex: 'remainLimit',\n      sorter: true,\n      width: 85,\n    },\n    {\n      title: t('account.apr'),\n      dataIndex: 'apr',\n      sorter: true,\n      width: 100,\n    },\n    accountIncludeCol(toggleIncludeHandler),\n    accountExpenseableCol(toggleExpenseableHandler),\n    accountIncomeableCol(toggleIncomeableHandler),\n    accountTransferToCol(toggleTransferToAbleHandler),\n    accountTransferFromCol(toggleTransferFromAbleHandler),\n    accountEnableCol(toggleHandler),\n    {\n      title: t('operation'),\n      key: 'operation',\n      align: 'center',\n      width: 150,\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={() => updateHandler(record)}>{t('update')}</Button>\n            <Button size=\"small\" onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n            <Button size=\"small\" onClick={() => adjustBalanceHandler(record)}>{t('adjust.balance')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      size=\"small\"\n      columns={columns}\n      expandable={{\n        expandedRowRender: record => <Descriptions size=\"small\" bordered column={5}>\n          <Descriptions.Item label={t('account.card.no')}>{record.no ? record.no : t('null')}</Descriptions.Item>\n          <Descriptions.Item label={t('account.initial.balance')}>{record.initialBalance}</Descriptions.Item>\n          <Descriptions.Item label={t('notes')}>{record.notes ? record.notes : t('null')}</Descriptions.Item>\n        </Descriptions>,\n      }}\n      dataSource={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      loading={queryLoading}\n      onChange={tableChangeHandler}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/OperationBar.jsx",
    "content": "import { useDispatch, useSelector } from 'umi';\nimport { Radio, Button, Space } from 'antd';\nimport {PlusOutlined, ReloadOutlined} from \"@ant-design/icons\";\nimport CheckingAccountModal from './CheckingAccountModal';\nimport CreditAccountModal from './CreditAccountModal';\nimport DebtAccountModal from './DebtAccountModal';\nimport AssetAccountModal from './AssetAccountModal';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const { currentAccountType } = useSelector(state => state.accounts);\n\n  const options = [\n    { label: t('menu.checking.account'), value: 1 },\n    { label: t('menu.credit.account'), value: 2 },\n    { label: t('menu.debt.account'), value: 3 },\n    { label: t('menu.asset.account'), value: 4 },\n  ];\n\n  const typeChangeHandler = (e) => {\n    dispatch({ type: 'accounts/updateState', payload: { currentAccountType: e.target.value }})\n  }\n\n  const addAccountHandler = () => {\n    switch (currentAccountType) {\n      case 1:\n        dispatch({ type: 'modal/show', payload: {component: CheckingAccountModal, type: 1, currentItem: { } }});\n        break;\n      case 2:\n        dispatch({ type: 'modal/show', payload: {component: CreditAccountModal, type: 1, currentItem: { } }});\n        break;\n      case 3:\n        dispatch({ type: 'modal/show', payload: {component: DebtAccountModal, type: 1, currentItem: { } }});\n        break;\n      case 4:\n        dispatch({ type: 'modal/show', payload: {component: AssetAccountModal, type: 1, currentItem: { } }});\n        break;\n    }\n  }\n\n  const refreshHandler = () => {\n    dispatch({ type: 'accounts/refresh' });\n  }\n\n  return (\n    <Space size=\"large\">\n      <Radio.Group\n        size=\"large\"\n        onChange={typeChangeHandler}\n        options={options}\n        value={currentAccountType}\n        optionType=\"button\"\n        buttonStyle=\"solid\" />\n      <Button size=\"large\" type=\"primary\" icon={<PlusOutlined />} onClick={ addAccountHandler }>{t('new')}</Button>\n      <Button size=\"large\" type=\"primary\" icon={<ReloadOutlined />} onClick={ refreshHandler }>{t('reload')}</Button>\n    </Space>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/index.jsx",
    "content": "import { useEffect } from \"react\";\nimport { Space } from 'antd';\nimport { useDispatch, useSelector } from 'umi';\nimport Breadcrumb from '@/components/Breadcrumb';\nimport { spaceVProp }  from '@/utils/var';\nimport OperationBar from './OperationBar';\nimport CheckingAccountTable from './CheckingAccountTable';\nimport CreditAccountTable from './CreditAccountTable';\nimport DebtAccountTable from './DebtAccountTable';\nimport AssetAccountTable from './AssetAccountTable';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const { currentAccountType } = useSelector(state => state.accounts);\n\n  const tableSwitch = (type) => {\n    switch(type) {\n      case 1:\n        return <CheckingAccountTable />;\n      case 2:\n        return <CreditAccountTable />;\n      case 3:\n        return <DebtAccountTable />;\n      case 4:\n        return <AssetAccountTable />;\n    }\n  }\n\n  return (\n    <Space {...spaceVProp} size=\"large\">\n      <Breadcrumb data={[t('menu.account.list'), t('menu.account.overview')]} />\n      <OperationBar />\n      { tableSwitch(currentAccountType) }\n    </Space>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/accounts/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { query as queryCheckingAccount } from '@/services/checking-account';\nimport { query as queryCreditAccount } from '@/services/credit-account';\nimport { query as queryDebtAccount } from '@/services/debt-account';\nimport { query as queryAssetAccount } from '@/services/asset-account';\nimport {tableChangeQueryFormat} from '@/utils/util';\n\nexport default modelExtend(model, {\n  namespace: 'accounts',\n  state: {\n    currentAccountType: 1,\n\n    checkingAccountPagination: { },\n    checkingAccountSorter: { },\n    queryCheckingAccountResponse: undefined,\n\n    creditAccountPagination: { },\n    creditAccountSorter: { },\n    queryCreditAccountResponse: undefined,\n\n    debtAccountPagination: { },\n    debtAccountSorter: { },\n    queryDebtAccountResponse: undefined,\n\n    assetAccountPagination: { },\n    assetAccountSorter: { },\n    queryAssetAccountResponse: undefined,\n\n  },\n  effects: {\n    *queryCheckingAccount({ payload }, { call, put }) {\n      const response = yield call(queryCheckingAccount, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryCheckingAccountResponse: response },\n      });\n    },\n    *queryCreditAccount({ payload }, { call, put }) {\n      const response = yield call(queryCreditAccount, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryCreditAccountResponse: response },\n      });\n    },\n    *queryDebtAccount({ payload }, { call, put }) {\n      const response = yield call(queryDebtAccount, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryDebtAccountResponse: response },\n      });\n    },\n    *queryAssetAccount({ payload }, { call, put }) {\n      const response = yield call(queryAssetAccount, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryAssetAccountResponse: response },\n      });\n    },\n    *refresh(_, { __, put, select }) {\n      const {\n        currentAccountType,\n        checkingAccountPagination,\n        checkingAccountSorter,\n        creditAccountPagination,\n        creditAccountSorter,\n        debtAccountPagination,\n        debtAccountSorter,\n        assetAccountPagination,\n        assetAccountSorter\n      } = yield select(state => state.accounts);\n      switch (currentAccountType) {\n        case 1:\n          yield put({ type: 'queryCheckingAccount', payload: tableChangeQueryFormat(checkingAccountPagination, checkingAccountSorter) });\n          break;\n        case 2:\n          yield put({ type: 'queryCreditAccount', payload: tableChangeQueryFormat(creditAccountPagination, creditAccountSorter) });\n          break;\n        case 3:\n          yield put({ type: 'queryDebtAccount', payload: tableChangeQueryFormat(debtAccountPagination, debtAccountSorter) });\n          break;\n        case 4:\n          yield put({ type: 'queryAssetAccount', payload: tableChangeQueryFormat(assetAccountPagination, assetAccountSorter) });\n          break;\n      }\n    }\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/asset-accounts/GeneralBar.jsx",
    "content": "import { useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {ReloadOutlined} from '@ant-design/icons';\nimport { useResponseData } from '@/utils/hooks';\nimport {Button, Card, Col, Row, Statistic} from \"antd\";\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'assetAccounts/sum' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['assetAccounts/sum']);\n  const queryLoading = useSelector(state => state.loading.effects['assetAccounts/query']);\n  const { sumResponse } = useSelector(state => state.assetAccounts);\n  const [sum] = useResponseData(sumResponse);\n\n  function refreshHandler() {\n    dispatch({ type: 'assetAccounts/refresh' });\n  }\n\n  return (\n    <Row gutter={16} align=\"middle\">\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('gross.balance')} value={sum.balance} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col flex=\"auto\" style={{ textAlign: \"right\" }}>\n        <Button type=\"primary\" size=\"large\" loading={queryLoading || loading} icon={<ReloadOutlined />} onClick={ refreshHandler }>{t('reload')}</Button>\n      </Col>\n    </Row>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/asset-accounts/ItemCard.jsx",
    "content": "import {useEffect} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport {Card, Row, Col, Button, Space, Descriptions, Collapse} from 'antd';\nimport { spaceVProp }  from '@/utils/var';\nimport AccountRecordTable from \"@/components/AccountRecordTable\";\nimport {showFlowModal} from \"@/utils/flow\";\nimport styles from './index.less';\nimport t from \"@/utils/translate\";\n\nexport default (props) => {\n\n  const dispatch = useDispatch();\n  const { account } = props;\n  const { currentTime } = useSelector(state => state.assetAccounts);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n  function expenseHandler() {\n    showFlowModal(1, 1, {accountId: account.id});\n  }\n\n  function incomeHandler() {\n    showFlowModal(2, 1, {accountId: account.id});\n  }\n\n  function transferToHandler() {\n    showFlowModal(3, 1, { toId: account.id });\n  }\n\n  function transferFromHandler() {\n    showFlowModal(3, 1, { fromId: account.id });\n  }\n\n  function adjustBalanceHandler() {\n    showFlowModal(4, 2, { ...account });\n  }\n\n  return (\n    <Row gutter={16}>\n      <Col span={24}>\n          <Card title={account.name} bordered={false} size=\"small\">\n            <Space {...spaceVProp}>\n              <Descriptions bordered={true} size=\"small\" column={3}>\n                <Descriptions.Item label={t('account.balance')}>{account.balance}</Descriptions.Item>\n                <Descriptions.Item label={t('currency')}>{account.currencyCode}</Descriptions.Item>\n                <Descriptions.Item label={t('account.include')}>{account.include ? t('yes') : t('no')}</Descriptions.Item>\n              </Descriptions>\n              <Space size=\"small\">\n                <Button disabled={!account.expenseable} size=\"small\" type=\"primary\" onClick={expenseHandler}>{t('expense')}</Button>\n                <Button disabled={!account.incomeable} size=\"small\" type=\"primary\" onClick={incomeHandler}>{t('income')}</Button>\n                <Button disabled={!account.transferToAble} size=\"small\" type=\"primary\" onClick={transferToHandler}>{t('transfer.to')}</Button>\n                <Button disabled={!account.transferFromAble} size=\"small\" type=\"primary\" onClick={transferFromHandler}>{t('transfer.from')}</Button>\n                <Button size=\"small\" type=\"primary\" onClick={adjustBalanceHandler}>{t('adjust.balance')}</Button>\n              </Space>\n              <Collapse className={styles['item-card-collapse']}>\n                <Collapse.Panel key={1} header={t('flow.record')}>\n                  <AccountRecordTable accountId={account.id} currentTime={currentTime} />\n                </Collapse.Panel>\n              </Collapse>\n            </Space>\n          </Card>\n      </Col>\n    </Row>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/asset-accounts/List.jsx",
    "content": "import { useEffect } from \"react\";\nimport {Empty, Pagination, Space, Spin} from \"antd\";\nimport { useDispatch, useSelector } from \"umi\";\nimport {spaceVProp} from \"@/utils/var\";\nimport { usePaginationAndData } from \"@/utils/hooks\";\nimport { paginationChange } from \"@/utils/util\";\nimport ItemCard from \"./ItemCard\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryResponse } = useSelector(state => state.assetAccounts);\n  const queryLoading = useSelector(state => state.loading.effects['assetAccounts/query']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n\n  return (\n    <Spin spinning={queryLoading} size=\"large\">\n      { dataAndPagination.data && dataAndPagination.data.length > 0 ?\n        (\n          <Space {...spaceVProp}>\n            { dataAndPagination.data.map(item => <ItemCard key={item.id} account={item} />) }\n            <Pagination {...dataAndPagination.pagination} onChange={paginationChange} />\n          </Space>\n        ) :\n        (<Empty style={{ marginTop: 50 }} />)\n      }\n    </Spin>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/asset-accounts/index.jsx",
    "content": "import { Space } from 'antd';\nimport { spaceVProp }  from '@/utils/var';\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport GeneralBar from './GeneralBar';\nimport List from './List';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.account.list'), t('menu.asset.account')]} />\n      <GeneralBar />\n      <List />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/asset-accounts/index.less",
    "content": ".item-card-collapse {\n  :global {\n    .ant-collapse-content-box {\n      padding: 0 !important;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/asset-accounts/model.js",
    "content": "import {history} from \"umi\";\nimport modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport {query, sum} from '@/services/asset-account';\n\n\nexport default modelExtend(model, {\n  namespace: 'assetAccounts',\n  state: {\n    queryResponse: undefined,\n    sumResponse: undefined,\n    currentTime: Date.now()\n  },\n  effects: {\n    *sum(_, { call, put }) {\n      const response = yield call(sum);\n      yield put({\n        type: 'updateState',\n        payload: { sumResponse: response },\n      });\n    },\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, { ...payload, ...{ enable: true } });\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n    *refresh(_, { call, put, select }) {\n      yield put({ type: 'query', payload: history.location.query });\n      yield put({ type: 'sum' });\n      // 更新账户的流水记录\n      yield put({ type: 'updateState', payload: {currentTime: new Date()} });\n    }\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/asset-accounts') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/audit/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport { Form, Row, Col, Button, Select, Space } from 'antd';\nimport { filterFormProp } from \"@/utils/var\";\nimport {searchHandler} from \"@/utils/util\";\nimport {useResponseSelectData, useCategoryTreeSelectData} from \"@/utils/hooks\";\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAmountRange from \"@/components/FormItemAmountRange\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport FormItemStatus from \"@/components/FormItemStatus\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['audit/query']);\n\n  useEffect(() => {\n    if (!accountResponse) dispatch({ type: 'account/fetchEnable' });\n  }, []);\n\n  const { getEnableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseSelectData(accountResponse);\n\n  const [dateRadioValue, setDateRadioValue] = useState();\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={20}>\n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemAmountRange />\n        <FormItemDescriptionSearch />\n        <Col flex=\"200px\">\n          <Form.Item label={t('flow.type')} name=\"type\">\n            <Select allowClear={true}>\n              <Select.Option value={1}>{t('expense')}</Select.Option>\n              <Select.Option value={2}>{t('income')}</Select.Option>\n              <Select.Option value={3}>{t('transfer')}</Select.Option>\n              <Select.Option value={4}>{t('adjust.balance')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n        <FormItemAccounts data={accounts} />\n        <FormItemStatus />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/audit/RecordTable.jsx",
    "content": "import { useSelector } from 'umi';\nimport {useResultPaginationAndData} from '@/utils/hooks';\nimport {handleTableChange} from \"@/utils/util\";\nimport FlowRecordTable from '@/components/FlowRecordTable';\n\nexport default () => {\n\n  const queryLoading = useSelector(state => state.loading.effects['audit/query']);\n  const { queryResponse } = useSelector(state => state.audit);\n  const [ dataAndPagination ] = useResultPaginationAndData(queryResponse);\n\n  return (\n    <FlowRecordTable\n      bordered={true}\n      data={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      queryLoading={queryLoading}\n      tableChangeHandler={handleTableChange}\n    />\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/audit/TotalAlert.jsx",
    "content": "import {useMemo} from \"react\";\nimport { useSelector } from 'umi';\nimport { Space } from 'antd';\nimport {useResponseData} from \"@/utils/hooks\";\nimport AlertTotalSearch from \"@/components/AlertTotalSearch\";\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const { queryResponse } = useSelector(state => state.audit);\n  const [ responseData ] = useResponseData(queryResponse);\n  const total = useMemo(() => {\n    if (responseData.result) {\n      return [responseData.expense, responseData.income, responseData.surplus]\n    } else {\n      return [0, 0, 0]\n    }\n  }, [responseData]);\n\n  return (\n    <Space>\n      <AlertTotalSearch message={t('gross.expense') + ': ' + total[0]} />\n      <AlertTotalSearch message={t('gross.income') + ': ' + total[1]} />\n      <AlertTotalSearch message={t('gross.surplus') + ': ' + total[2]} />\n    </Space>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/audit/index.jsx",
    "content": "import { Space } from 'antd';\nimport {spaceVProp} from '@/utils/var';\nimport Breadcrumb from '@/components/Breadcrumb';\nimport OperationBar from './OperationBar';\nimport TotalAlert from './TotalAlert';\nimport RecordTable from './RecordTable';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.bookkeeping'), t('menu.audit')]} />\n      <OperationBar />\n      <TotalAlert />\n      <RecordTable />\n    </Space>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/audit/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport { model } from '@/utils/model';\nimport { audit } from '@/services/flow';\n\nexport default modelExtend(model, {\n  namespace: 'audit',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(audit, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/audit') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/balance-logs/OperationBar.jsx",
    "content": "import { useDispatch } from 'umi';\nimport { Button } from 'antd';\nimport OperationModal from './OperationModal';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const addHandler = () => {\n    dispatch({ type: 'modal/show', payload: {component: OperationModal, type: 1, currentItem: {} }});\n  }\n\n  return (\n    <Button type=\"primary\" onClick={ addHandler }>{t('new')}</Button>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/balance-logs/OperationModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport {history, useDispatch, useSelector} from 'umi';\nimport {DatePicker, Form, Input} from 'antd';\nimport moment from \"moment\";\nimport { create } from '@/services/log';\nimport {getNull} from \"@/utils/util\";\nimport {balanceRequiredRules, timeRequiredRules} from \"@/utils/rules\";\nimport FormModal from \"@/components/FormModal\";\nimport styles from './index.less';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible } = useSelector(state => state.modal);\n\n  const [initialValues, setInitialValues] = useState({});\n  useEffect(() => {\n    if (!visible) return;\n    setInitialValues({\n      ...getNull(form.getFieldsValue()),\n      'createTime': moment(),\n    });\n  }, [visible]);\n\n  function successHandler() {\n    dispatch({ type: 'logs/query', payload: history.location.query });\n  }\n\n  return (\n    <FormModal\n      title={t('new') + t('balance.log')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={create}\n      onSuccess={successHandler}\n    >\n      <Form form={form} className={styles['form3']}>\n        <Form.Item label={t('flow.createTime')} name=\"createTime\" rules={timeRequiredRules()}>\n          <DatePicker showTime={false} format='YYYY-MM-DD' />\n        </Form.Item>\n        <Form.Item label={t('asset.balance')} name=\"asset\" rules={balanceRequiredRules()}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('debt.balance')} name=\"debt\" rules={balanceRequiredRules()}>\n          <Input />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/balance-logs/RecordTable.jsx",
    "content": "import {useEffect} from \"react\";\nimport {history, useDispatch, useIntl, useSelector} from 'umi';\nimport { Button, message, Table, Space, Modal } from \"antd\";\nimport {tableProp} from \"@/utils/var\";\nimport { remove } from '@/services/log';\nimport {getTimeFormat, handleTableChange} from \"@/utils/util\";\nimport {usePaginationAndData} from \"@/utils/hooks\";\nimport t from \"@/utils/translate\";\nimport moment from \"moment\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryResponse } = useSelector(state => state.logs);\n  const queryLoading = useSelector(state => state.loading.effects['logs/query']);\n  const [dataAndPagination] = usePaginationAndData(queryResponse);\n\n  const intl = useIntl();\n  const messageOperationSuccess = t('operation.success');\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'logs/query', payload: history.location.query });\n        }\n      }\n    });\n  }\n\n  const columns = [\n    {\n      title: t('flow.createTime'),\n      dataIndex: 'createTime',\n      sorter: true,\n      width: 120,\n      render: time => moment(time).format('YYYY-MM-DD')\n    },\n    {\n      title:  t('asset.balance'),\n      dataIndex: 'asset',\n    },\n    {\n      title:  t('debt.balance'),\n      dataIndex: 'debt',\n    },\n    {\n      title: t('operation'),\n      key: 'operation',\n      align: 'center',\n      width: 150,\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      columns={columns}\n      dataSource={dataAndPagination.data}\n      loading={queryLoading}\n      pagination={dataAndPagination.pagination}\n      onChange={handleTableChange}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/balance-logs/index.jsx",
    "content": "import {Space} from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport RecordTable from './RecordTable';\nimport OperationBar from './OperationBar';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp} size=\"large\">\n      <Breadcrumb data={[t('menu.account.list'), t('balance.log')]} />\n      <OperationBar />\n      <RecordTable />\n    </Space>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/balance-logs/index.less",
    "content": ".form3 {\n  :global {\n    .ant-form-item-label {\n      width: 70px;\n    }\n  }\n}\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/balance-logs/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport { model } from '@/utils/model';\nimport { query } from '@/services/log';\n\nexport default modelExtend(model, {\n  namespace: 'logs',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/balance-logs') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/books/ConfigModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Select, TreeSelect, Switch} from 'antd';\nimport {config} from '@/services/book';\nimport {useCategoryTreeSelectData, useResponseSelectData} from \"@/utils/hooks\";\nimport {getNull} from \"@/utils/util\";\nimport FormModal from \"@/components/FormModal\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, currentItem } = useSelector(state => state.modal);\n\n  const { getExpenseableResponse } = useSelector(state => state.account);\n  const [expenseAccounts] = useResponseSelectData(getExpenseableResponse);\n\n  const { getIncomeableResponse } = useSelector(state => state.account);\n  const [incomeAccounts] = useResponseSelectData(getIncomeableResponse);\n\n  const { getTransferFromAbleResponse } = useSelector(state => state.account);\n  const [transferFromAccounts] = useResponseSelectData(getTransferFromAbleResponse);\n\n  const { getTransferToAbleResponse } = useSelector(state => state.account);\n  const [transferToAccounts] = useResponseSelectData(getTransferToAbleResponse);\n\n  const { querySimpleResponse : expenseCategoryResponse } = useSelector(state => state.expenseCategory);\n  const [expenseCategories] = useCategoryTreeSelectData(expenseCategoryResponse);\n\n  const { querySimpleResponse : incomeCategoryResponse } = useSelector(state => state.incomeCategory);\n  const [incomeCategories] = useCategoryTreeSelectData(incomeCategoryResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!getExpenseableResponse) dispatch({ type: 'account/fetchExpenseable' });\n      if (!getIncomeableResponse) dispatch({ type: 'account/fetchIncomeable' });\n      if (!getTransferFromAbleResponse) dispatch({ type: 'account/fetchTransferFromAble' });\n      if (!getTransferToAbleResponse) dispatch({ type: 'account/fetchTransferToAble' });\n      if (!expenseCategoryResponse) dispatch({ type: 'expenseCategory/fetchSimple' });\n      if (!incomeCategoryResponse) dispatch({ type: 'incomeCategory/fetchSimple' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({})\n  useEffect(() => {\n    if (visible) {\n      let currentItemCopy = {...currentItem};\n      currentItemCopy.defaultExpenseAccountId = currentItemCopy.defaultExpenseAccount ? currentItemCopy.defaultExpenseAccount.id : null;\n      currentItemCopy.defaultIncomeAccountId = currentItemCopy.defaultIncomeAccount ? currentItemCopy.defaultIncomeAccount.id : null;\n      currentItemCopy.defaultTransferFromAccountId = currentItemCopy.defaultTransferFromAccount ? currentItemCopy.defaultTransferFromAccount.id : null;\n      currentItemCopy.defaultTransferToAccountId = currentItemCopy.defaultTransferToAccount ? currentItemCopy.defaultTransferToAccount.id : null;\n      currentItemCopy.defaultExpenseCategoryId = currentItemCopy.defaultExpenseCategory ? currentItemCopy.defaultExpenseCategory.id : null;\n      currentItemCopy.defaultIncomeCategoryId = currentItemCopy.defaultIncomeCategory ? currentItemCopy.defaultIncomeCategory.id : null;\n      setInitialValues({...getNull(form.getFieldsValue()), ...currentItemCopy});\n    }\n  }, [visible]);\n\n  function successHandler(response) {\n    dispatch({ type: 'books/query' });\n    dispatch({\n      type: 'session/updateState',\n      payload: { defaultBook: response.data }\n    });\n  }\n\n  return (\n    <FormModal\n      title={t('config') + t('book')}\n      form={form}\n      initialValues={initialValues}\n      update={config}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form form={form}>\n        <Form.Item label={t('book.description.eable')} valuePropName=\"checked\" name=\"descriptionEnable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('book.time.eable')} valuePropName=\"checked\" name=\"timeEnable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('book.image.eable')} valuePropName=\"checked\" name=\"imageEnable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('book.default.expense.account')} name=\"defaultExpenseAccountId\">\n          <Select options={expenseAccounts} showArrow allowClear />\n        </Form.Item>\n        <Form.Item label={t('book.default.income.account')} name=\"defaultIncomeAccountId\">\n          <Select options={incomeAccounts} showArrow allowClear />\n        </Form.Item>\n        <Form.Item label={t('book.default.transfer.from.account')} name=\"defaultTransferFromAccountId\">\n          <Select options={transferFromAccounts} showArrow allowClear />\n        </Form.Item>\n        <Form.Item label={t('book.default.transfer.to.account')} name=\"defaultTransferToAccountId\">\n          <Select options={transferToAccounts} showArrow allowClear />\n        </Form.Item>\n        <Form.Item label={t('book.default.expense.category')} name=\"defaultExpenseCategoryId\">\n          <TreeSelect\n            treeDataSimpleMode={true}\n            treeData={expenseCategories}\n            showArrow={true}\n            showSearch={true}\n            treeNodeFilterProp=\"title\"\n            allowClear={true} />\n        </Form.Item>\n        <Form.Item label={t('book.default.income.category')} name=\"defaultIncomeCategoryId\">\n          <TreeSelect\n            treeDataSimpleMode={true}\n            treeData={incomeCategories}\n            showArrow={true}\n            showSearch={true}\n            treeNodeFilterProp=\"title\"\n            allowClear={true} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/books/OperationBar.jsx",
    "content": "import { useDispatch } from 'umi';\nimport { Button } from 'antd';\nimport OperationModal from './OperationModal';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const addHandler = () => {\n    dispatch({ type: 'modal/show', payload: {component: OperationModal, type: 1, currentItem: null }});\n  }\n\n  return (\n    <Button type=\"primary\" onClick={ addHandler }>{t('new')}</Button>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/books/OperationModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Input, Select, Switch} from 'antd';\nimport { create, update } from '@/services/book';\nimport {getNull} from \"@/utils/util\";\nimport {nameRules, notesRules, requiredRules} from \"@/utils/rules\";\nimport FormModal from \"@/components/FormModal\";\nimport styles from './index.less';\nimport t from \"@/utils/translate\";\nimport {useCurrencyResponseSelectData} from \"@/utils/hooks\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const [initialValues, setInitialValues] = useState(currentItem)\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        ...{\n          defaultCurrencyCode: defaultGroup.defaultCurrencyCode,\n          descriptionEnable: true,\n          timeEnable: false,\n          imageEnable: false,\n        }\n      });\n    } else {\n      setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n    }\n  }, [visible]);\n\n  useEffect(() => {\n    if (!currencyResponse) dispatch({ type: 'currency/fetchAll' });\n  }, []);\n  const { getAllResponse : currencyResponse } = useSelector(state => state.currency);\n  const [currencyList] = useCurrencyResponseSelectData(currencyResponse);\n\n  const { defaultGroup } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n\n  function successHandler() {\n    dispatch({ type: 'books/query' });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('book')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={successHandler}\n    >\n      <Form form={form} className={styles['form3']}>\n        <Form.Item label={t('book.name')} name=\"name\" rules={nameRules()}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('default.currency')} name=\"defaultCurrencyCode\" rules={requiredRules()}>\n          <Select options={currencyList} showArrow showSearch filterOption optionFilterProp={\"label\"} disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('book.description.eable')} valuePropName=\"checked\" name=\"descriptionEnable\">\n          <Switch disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('book.time.eable')} valuePropName=\"checked\" name=\"timeEnable\">\n          <Switch disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('book.image.eable')} valuePropName=\"checked\" name=\"imageEnable\">\n          <Switch disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/books/RecordTable.jsx",
    "content": "import {useEffect} from \"react\";\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport {Button, message, Table, Space, Modal, Switch} from \"antd\";\nimport { remove, toggle } from '@/services/book';\nimport { setDefaultBook } from '@/services/user';\nimport {tableProp} from \"@/utils/var\";\nimport { handleTableChange } from \"@/utils/util\";\nimport {usePaginationAndData} from \"@/utils/hooks\";\nimport FlagTag from '@/components/FlagTag';\nimport OperationModal from '@/pages/books/OperationModal';\nimport ConfigModal from '@/pages/books/ConfigModal';\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { defaultBook } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultBook) dispatch({ type: 'session/fetchSession' });\n  }, []);\n\n  const { queryResponse } = useSelector(state => state.books);\n  const queryLoading = useSelector(state => state.loading.effects['books/query']);\n  const [dataAndPagination] = usePaginationAndData(queryResponse);\n\n  const updateHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: OperationModal, type: 2, currentItem: record } });\n  }\n\n  const configHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: ConfigModal, type: 2, currentItem: record } });\n  }\n\n  const intl = useIntl();\n  const messageOperationSuccess = t('operation.success');\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'books/query' });\n        }\n      }\n    });\n  }\n\n  const setDefaultHandler = async (record) => {\n    const response = await setDefaultBook(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      window.location.reload();\n    }\n  }\n\n  const toggleHandler = async (record) => {\n    const response = await toggle(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      dispatch({ type: 'books/query' });\n    }\n  }\n\n  const columns = [\n    {\n      title: t('book.name'),\n      dataIndex: 'name',\n    },\n    {\n      title: t('default.currency'),\n      dataIndex: 'defaultCurrencyCode',\n      width: 70,\n    },\n    {\n      title: t('book.group'),\n      dataIndex: 'group',\n      render: value => <>{value ? value.name : null}</>\n    },\n    {\n      title: t('book.description.eable'),\n      dataIndex: 'descriptionEnable',\n      width: 90,\n      render: value => <FlagTag value={value} />\n    },\n    {\n      title: t('book.time.eable'),\n      dataIndex: 'timeEnable',\n      width: 90,\n      render: value => <FlagTag value={value} />\n    },\n    {\n      title: t('book.image.eable'),\n      dataIndex: 'imageEnable',\n      width: 90,\n      render: value => <FlagTag value={value} />\n    },\n    {\n      title: t('book.default.expense.account'),\n      dataIndex: 'defaultExpenseAccount',\n      render: value => <>{value ? value.name : null}</>\n    },\n    {\n      title: t('book.default.income.account'),\n      dataIndex: 'defaultIncomeAccount',\n      render: value => <>{value ? value.name : null}</>\n    },\n    {\n      title: t('book.default.expense.category'),\n      dataIndex: 'defaultExpenseCategory',\n      render: value => <>{value ? value.name : null}</>\n    },\n    {\n      title: t('book.default.income.category'),\n      dataIndex: 'defaultIncomeCategory',\n      render: value => <>{value ? value.name : null}</>\n    },\n    {\n      title: t('book.default.transfer.from.account'),\n      dataIndex: 'defaultTransferFromAccount',\n      render: value => <>{value ? value.name : null}</>\n    },\n    {\n      title: t('book.default.transfer.to.account'),\n      dataIndex: 'defaultTransferToAccount',\n      render: value => <>{value ? value.name : null}</>\n    },\n    {\n      title: t('is.enable'),\n      dataIndex: 'enable',\n      width: 80,\n      render: (value, record) => <Switch disabled={defaultBook && record.id === defaultBook.id} onChange={() => toggleHandler(record)} checked={value} />\n    },\n    {\n      title: t('operation'),\n      key: 'operation',\n      align: 'center',\n      width: 230,\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={() => updateHandler(record)}>{t('update')}</Button>\n            <Button size=\"small\" disabled={defaultBook && record.id === defaultBook.id} onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n            <Button size=\"small\" disabled={!defaultBook || record.id !== defaultBook.id} onClick={() => configHandler(record)}>{t('config')}</Button>\n            <Button size=\"small\" disabled={defaultBook && record.id === defaultBook.id} onClick={() => setDefaultHandler(record)}>{t('set.default')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      rowClassName={record => {\n        if (defaultBook && record.id === defaultBook.id) return 'table-color-default';\n      }}\n      size=\"small\"\n      columns={columns}\n      dataSource={dataAndPagination.data}\n      loading={queryLoading}\n      pagination={dataAndPagination.pagination}\n      onChange={handleTableChange}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/books/index.jsx",
    "content": "import {Space} from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport RecordTable from './RecordTable';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp} size=\"large\">\n      <Breadcrumb data={[t('menu.settings'), t('menu.book')]} />\n      <OperationBar />\n      <RecordTable />\n    </Space>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/books/index.less",
    "content": ".form3 {\n  :global {\n    .ant-form-item-label {\n      width: 100px;\n    }\n  }\n}\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/books/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport { model } from '@/utils/model';\nimport { query } from '@/services/book';\n\nexport default modelExtend(model, {\n  namespace: 'books',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/books') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/ExpenseCategoryFilterBar.jsx",
    "content": "import {Button, Col, Form, Input, Row, Select, Space} from \"antd\";\nimport {useDispatch} from \"umi\";\nimport {filterFormProp} from \"@/utils/var\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const [form] = Form.useForm();\n  const dispatch = useDispatch();\n\n  async function resetHandler() {\n    form.resetFields();\n    const values = await form.validateFields();\n    dispatch({ type: 'categories/updateState', payload: {expenseCategoryQueryData: values} });\n  }\n\n  async function searchHandler(form) {\n    const values = await form.validateFields();\n    dispatch({ type: 'categories/queryExpenseCategory', payload: values });\n    dispatch({ type: 'categories/updateState', payload: {expenseCategoryQueryData: values} });\n  }\n\n  return (\n    <Form {...filterFormProp} form={form} size=\"small\">\n      <Row gutter={20}>\n        <Col flex=\"300px\">\n          <Form.Item label={t('name')} name=\"name\">\n            <Input allowClear={true} />\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('is.enable')} name=\"enable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n      </Row>\n      <Row gutter={20} justify=\"end\">\n        <Col span={24} style={{textAlign: \"right\"}}>\n          <Space>\n            <Button type=\"primary\" size=\"small\" onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" size=\"small\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/ExpenseCategoryModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport { Form, Input, TreeSelect, message } from 'antd';\nimport { create, update } from '@/services/expense-category';\nimport { useCategoryTreeSelectData } from \"@/utils/hooks\";\nimport {getNull, validateForm} from \"@/utils/util\";\nimport FormModal from \"@/components/FormModal\";\nimport {nameRules, notesRules} from \"@/utils/rules\";\nimport t from \"@/utils/translate\";\nimport styles from './index.less';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const { querySimpleResponse : categoryResponse } = useSelector(state => state.expenseCategory);\n  const [treeData] = useCategoryTreeSelectData(categoryResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!categoryResponse) dispatch({ type: 'expenseCategory/fetchSimple' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({})\n  useEffect(() => {\n    if (visible) {\n      if (type === 1) {\n        setInitialValues({\n          ...getNull(form.getFieldsValue()),\n          ...{\n            parentId: currentItem && currentItem.id ? currentItem.id : null,\n          }\n        });\n      } else {\n        setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n      }\n    }\n  }, [visible]);\n\n  function successHandler() {\n    dispatch({ type: 'categories/queryExpenseCategory' });\n    dispatch({ type: 'expenseCategory/refresh' });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('expense.category')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={successHandler}\n    >\n      <Form form={form} className={styles['form']}>\n        <Form.Item label={t('parent.category')} name=\"parentId\">\n          <TreeSelect\n            allowClear={true}\n            treeDataSimpleMode={true}\n            treeData={treeData}\n            showArrow={true}\n            showSearch={true}\n            treeNodeFilterProp=\"title\"\n            treeDefaultExpandAll={false} />\n        </Form.Item>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/ExpenseCategoryTable.jsx",
    "content": "import {useDispatch, useIntl, useSelector} from 'umi';\nimport {useEffect} from \"react\";\nimport {Button, message, Table, Space, Modal, Switch} from \"antd\";\nimport { remove, toggle } from '@/services/category';\nimport { useResponseData } from '@/utils/hooks';\nimport {searchTreeArray, tableChangeQueryFormat} from '@/utils/util';\nimport {tableProp} from '@/utils/var';\nimport ExpenseCategoryModal from './ExpenseCategoryModal';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'categories/queryExpenseCategory' });\n  }, []);\n\n  const { queryExpenseCategoryResponse : queryResponse } = useSelector(state => state.categories);\n  const queryLoading = useSelector(state => state.loading.effects['categories/queryExpenseCategory']);\n  const [categoryTreeData, setCategoryTreeData] = useResponseData(queryResponse);\n\n  const addHandler = (record, event) => {\n    dispatch({ type: 'modal/show', payload: {component: ExpenseCategoryModal, type: 1, currentItem: record }});\n    event.stopPropagation();\n  }\n\n  function refresh() {\n    dispatch({ type: 'categories/queryExpenseCategory' });\n    dispatch({ type: 'expenseCategory/refresh' });\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const toggleHandler = async (record) => {\n    // 乐观更新\n    let newCategoryTreeData = JSON.parse(JSON.stringify(categoryTreeData));\n    let node = searchTreeArray(newCategoryTreeData, record.id);\n    if (node) node.enable = !node.enable;\n    setCategoryTreeData(newCategoryTreeData);\n    const response = await toggle(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n\n  const updateHandler = (record, event) => {\n    dispatch({ type: 'modal/show', payload: {component: ExpenseCategoryModal, type: 2, currentItem: record }});\n    event.stopPropagation();\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record, event) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refresh();\n        }\n      }\n    });\n    event.stopPropagation();\n  }\n\n  const columns = [\n    {\n      title: t('name'),\n      dataIndex: 'name',\n    },\n    {\n      title: t('notes'),\n      dataIndex: 'notes',\n    },\n    {\n      title: t('is.enable'),\n      dataIndex: 'enable',\n      width: 150,\n      render: (value, record) => <Switch onClick = { (_, event) => event.stopPropagation() } onChange={ () => toggleHandler(record) } checked={value} />\n    },\n    {\n      title: t('operation'),\n      key: 'operation',\n      width: 150,\n      align: 'center',\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={(event) => updateHandler(record, event)}>{t('update')}</Button>\n            <Button size=\"small\" disabled={!record.enable} onClick={(event) => addHandler(record, event)}>{t('new')}</Button>\n            <Button size=\"small\" onClick={(event) => deleteHandler(record, event)}>{t('delete')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    categoryTreeData && categoryTreeData.length > 0 && <Table\n      {...tableProp}\n      size=\"middle\"\n      columns={columns}\n      dataSource={categoryTreeData}\n      loading={queryLoading}\n      expandable={{ expandRowByClick: true, defaultExpandAllRows: true }}\n      pagination={false}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/IncomeCategoryFilterBar.jsx",
    "content": "import {Button, Col, Form, Input, Row, Select, Space} from \"antd\";\nimport {useDispatch} from \"umi\";\nimport {filterFormProp} from \"@/utils/var\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const [form] = Form.useForm();\n  const dispatch = useDispatch();\n\n  async function resetHandler() {\n    form.resetFields();\n    const values = await form.validateFields();\n    dispatch({ type: 'categories/updateState', payload: {incomeCategoryQueryData: values} });\n  }\n\n  async function searchHandler(form) {\n    const values = await form.validateFields();\n    dispatch({ type: 'categories/queryIncomeCategory', payload: values });\n    dispatch({ type: 'categories/updateState', payload: {incomeCategoryQueryData: values} });\n  }\n\n  return (\n    <Form {...filterFormProp} form={form} size=\"small\">\n      <Row gutter={20}>\n        <Col flex=\"300px\">\n          <Form.Item label={t('name')} name=\"name\">\n            <Input allowClear={true} />\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('is.enable')} name=\"enable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n      </Row>\n      <Row gutter={20} justify=\"end\">\n        <Col span={24} style={{textAlign: \"right\"}}>\n          <Space>\n            <Button type=\"primary\" size=\"small\" onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" size=\"small\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/IncomeCategoryModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport { Form, Input, TreeSelect, message } from 'antd';\nimport { create, update } from '@/services/income-category';\nimport { useCategoryTreeSelectData } from \"@/utils/hooks\";\nimport {getNull, validateForm} from \"@/utils/util\";\nimport FormModal from \"@/components/FormModal\";\nimport {nameRules, notesRules} from \"@/utils/rules\";\nimport t from \"@/utils/translate\";\nimport styles from './index.less';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const { querySimpleResponse : categoryResponse } = useSelector(state => state.incomeCategory);\n  const [treeData] = useCategoryTreeSelectData(categoryResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!categoryResponse) dispatch({ type: 'incomeCategory/fetchSimple' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState(currentItem)\n  useEffect(() => {\n    if (visible) {\n      if (type === 1) {\n        setInitialValues({\n          ...getNull(form.getFieldsValue()),\n          ...{\n            parentId: currentItem && currentItem.id ? currentItem.id : null,\n          }\n        });\n      } else {\n        setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n      }\n    }\n  }, [visible]);\n\n  function successHandler() {\n    dispatch({ type: 'categories/queryIncomeCategory' });\n    dispatch({ type: 'incomeCategory/refresh' });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('income.category')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={successHandler}\n    >\n      <Form form={form} className={styles['form']}>\n        <Form.Item label={t('parent.category')} name=\"parentId\">\n          <TreeSelect\n            allowClear={true}\n            treeDataSimpleMode={true}\n            treeData={treeData}\n            showArrow={true}\n            showSearch={true}\n            treeNodeFilterProp=\"title\"\n            treeDefaultExpandAll={false} />\n        </Form.Item>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/IncomeCategoryTable.jsx",
    "content": "import {useDispatch, useIntl, useSelector} from 'umi';\nimport { Button, message, Table, Space, Modal, Switch } from \"antd\";\nimport { remove, toggle } from '@/services/category';\nimport { useResponseData } from \"@/utils/hooks\";\nimport {useEffect} from \"react\";\nimport {tableProp} from \"@/utils/var\";\nimport {searchTreeArray} from \"@/utils/util\";\nimport IncomeCategoryModal from './IncomeCategoryModal';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'categories/queryIncomeCategory' });\n  }, []);\n\n  const { queryIncomeCategoryResponse : queryResponse } = useSelector(state => state.categories);\n  const queryLoading = useSelector(state => state.loading.effects['categories/queryIncomeCategory']);\n  const [categoryTreeData, setCategoryTreeData] = useResponseData(queryResponse);\n\n  const addHandler = (record, event) => {\n    dispatch({ type: 'modal/show', payload: {component: IncomeCategoryModal, type: 1, currentItem: record }});\n    event.stopPropagation();\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const toggleHandler = async (record) => {\n    // 乐观更新\n    let newCategoryTreeData = JSON.parse(JSON.stringify(categoryTreeData));\n    let node = searchTreeArray(newCategoryTreeData, record.id);\n    if (node) node.enable = !node.enable;\n    setCategoryTreeData(newCategoryTreeData);\n    const response = await toggle(record.id);\n    if (response) {\n      if (response.success) {\n        message.success(messageOperationSuccess);\n        dispatch({ type: 'categories/queryIncomeCategory' });\n        dispatch({ type: 'incomeCategory/refresh' });\n      }\n    }\n  }\n\n  const updateHandler = (record, event) => {\n    dispatch({ type: 'modal/show', payload: {component: IncomeCategoryModal, type: 2, currentItem: record }});\n    event.stopPropagation();\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record, event) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'categories/queryIncomeCategory' });\n          dispatch({ type: 'incomeCategory/refresh' });\n        }\n      }\n    });\n    event.stopPropagation();\n  }\n\n  const columns = [\n    {\n      title: t('name'),\n      dataIndex: 'name',\n    },\n    {\n      title: t('notes'),\n      dataIndex: 'notes',\n    },\n    {\n      title: t('is.enable'),\n      dataIndex: 'enable',\n      width: 150,\n      render: (value, record) => <Switch onClick = { (_, event) => event.stopPropagation() } onChange={ () => toggleHandler(record) } checked={value} />\n    },\n    {\n      title: t('operation'),\n      key: 'operation',\n      width: 150,\n      align: 'center',\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={(event) => updateHandler(record, event)}>{t('update')}</Button>\n            <Button size=\"small\" disabled={!record.enable} onClick={(event) => addHandler(record, event)}>{t('new')}</Button>\n            <Button size=\"small\" onClick={(event) => deleteHandler(record, event)}>{t('delete')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    categoryTreeData && categoryTreeData.length > 0 && <Table\n      {...tableProp}\n      size=\"middle\"\n      columns={columns}\n      dataSource={categoryTreeData}\n      loading={queryLoading}\n      expandable={{ expandRowByClick: true, defaultExpandAllRows: true }}\n      pagination={false}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/OperationBar.jsx",
    "content": "import { useDispatch, useSelector } from 'umi';\nimport { Radio, Button, Space } from 'antd';\nimport { PlusOutlined } from \"@ant-design/icons\";\nimport ExpenseCategoryModal from './ExpenseCategoryModal';\nimport IncomeCategoryModal from './IncomeCategoryModal';\nimport TagModal from './TagModal';\nimport PayeeModal from './PayeeModal';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const { currentCategoryType } = useSelector(state => state.categories);\n\n  const options = [\n    { label: t('expense.category'), value: 1 },\n    { label: t('income.category'), value: 2 },\n    { label: t('tag'), value: 3 },\n    { label: t('payee'), value: 4 },\n  ];\n\n  const typeChangeHandler = (e) => {\n    dispatch({ type: 'categories/updateState', payload: { currentCategoryType: e.target.value }})\n  }\n\n  const addHandler = () => {\n    switch (currentCategoryType) {\n      case 1:\n        dispatch({ type: 'modal/show', payload: {component: ExpenseCategoryModal, type: 1, currentItem: {} }});\n        break;\n      case 2:\n        dispatch({ type: 'modal/show', payload: {component: IncomeCategoryModal, type: 1, currentItem: {} }});\n        break;\n      case 3:\n        dispatch({ type: 'modal/show', payload: {component: TagModal, type: 1, currentItem: {} }});\n        break;\n      case 4:\n        dispatch({ type: 'modal/show', payload: {component: PayeeModal, type: 1, currentItem: {} }});\n        break;\n    }\n  }\n\n  return (\n    <Space size=\"large\">\n      <Radio.Group\n        size=\"large\"\n        onChange={typeChangeHandler}\n        options={options}\n        value={currentCategoryType}\n        optionType=\"button\"\n        buttonStyle=\"solid\" />\n      <Button size=\"large\" type=\"primary\" onClick={addHandler} icon={<PlusOutlined />}>{t('new')}</Button>\n    </Space>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/PayeeFilterBar.jsx",
    "content": "import {Button, Col, Form, Input, Row, Select, Space} from \"antd\";\nimport {useDispatch} from \"umi\";\nimport {filterFormProp} from \"@/utils/var\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const [form] = Form.useForm();\n  const dispatch = useDispatch();\n\n  async function resetHandler() {\n    form.resetFields();\n    const values = await form.validateFields();\n    dispatch({ type: 'categories/updateState', payload: {payeeQueryData: values} });\n  }\n\n  async function searchHandler(form) {\n    const values = await form.validateFields();\n    dispatch({ type: 'categories/queryPayee', payload: values });\n    dispatch({ type: 'categories/updateState', payload: {payeeQueryData: values} });\n  }\n\n  return (\n    <Form {...filterFormProp} form={form} size=\"small\">\n      <Row gutter={20}>\n        <Col flex=\"300px\">\n          <Form.Item label={t('name')} name=\"name\">\n            <Input allowClear={true} />\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('is.enable')} name=\"enable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('expenseable')} name=\"expenseable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('incomeable')} name=\"incomeable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n      </Row>\n      <Row gutter={20} justify=\"end\">\n        <Col span={24} style={{textAlign: \"right\"}}>\n          <Space>\n            <Button type=\"primary\" size=\"small\" onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" size=\"small\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/PayeeModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Input, message, Switch} from 'antd';\nimport { create, update } from '@/services/payee';\nimport FormModal from \"@/components/FormModal\";\nimport {nameRules, notesRules} from \"@/utils/rules\";\nimport {getNull, tableChangeQueryFormat, validateForm} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\nimport styles from './index.less';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n  const { payeePagination : pagination, payeeSorter : sorter } = useSelector(state => state.categories);\n\n  const [initialValues, setInitialValues] = useState({})\n  useEffect(() => {\n    if (visible) {\n      if (type === 1) {\n        setInitialValues({\n          ...getNull(form.getFieldsValue()),\n          expenseable: true,\n          incomeable: false,\n        });\n      } else {\n        setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n      }\n    }\n  }, [visible]);\n\n  function successHandler(response) {\n    dispatch({ type: 'categories/queryPayee', payload: tableChangeQueryFormat(pagination, sorter) });\n    dispatch({ type: 'payee/refresh', payload: { ...response.data } });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('payee')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form form={form} className={styles['form3']}>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('expenseable')} valuePropName=\"checked\" name=\"expenseable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('incomeable')} valuePropName=\"checked\" name=\"incomeable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/PayeeTable.jsx",
    "content": "import {useEffect} from \"react\";\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport {Button, message, Table, Space, Modal, Switch} from \"antd\";\nimport {tableProp} from \"@/utils/var\";\nimport { tableChangeQueryFormat } from \"@/utils/util\";\nimport { categoryExpenseableCol, categoryIncomeableCol } from '@/utils/columns';\nimport { remove, toggle } from '@/services/payee';\nimport { usePaginationAndData } from '@/utils/hooks';\nimport PayeeModal from './PayeeModal';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryPayeeResponse : queryResponse } = useSelector(state => state.categories);\n  const queryLoading = useSelector(state => state.loading.effects['categories/queryPayee']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n  const { payeePagination : pagination, payeeSorter : sorter, payeeQueryData: query } = useSelector(state => state.categories);\n\n  useEffect(() => {\n    dispatch({ type: 'categories/queryPayee' });\n  }, []);\n\n  function tableChangeHandler(pagination, _, sorter) {\n    dispatch({ type: 'categories/updateState', payload: {payeePagination: pagination, payeeSorter: sorter} });\n    dispatch({ type: 'categories/queryPayee', payload: { ...tableChangeQueryFormat(pagination, sorter), ...query }});\n  }\n\n  function refresh(response) {\n    dispatch({ type: 'categories/queryPayee', payload: {...tableChangeQueryFormat(pagination, sorter), ...query }});\n    dispatch({ type: 'payee/refresh', payload: { ...response.data } });\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const toggleHandler = async (record) => {\n    const response = await toggle(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh(response);\n    }\n  }\n\n  const updateHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: PayeeModal, type: 2, currentItem: record }});\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refresh(response);\n        }\n      }\n    });\n  }\n\n  const columns = [\n    {\n      title: t('name'),\n      dataIndex: 'name',\n    },\n    {\n      title: t('notes'),\n      dataIndex: 'notes',\n    },\n    {\n      title: t('is.enable'),\n      dataIndex: 'enable',\n      width: 150,\n      render: (value, record) => <Switch onChange={() => toggleHandler(record)} checked={value} />\n    },\n    categoryExpenseableCol(),\n    categoryIncomeableCol(),\n    {\n      title: t('operation'),\n      key: 'operation',\n      width: 120,\n      align: 'center',\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={() => updateHandler(record)}>{t('update')}</Button>\n            <Button size=\"small\" onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      size=\"middle\"\n      columns={columns}\n      dataSource={dataAndPagination.data}\n      rowKey={record => record.id}\n      loading={queryLoading}\n      pagination={dataAndPagination.pagination}\n      onChange={tableChangeHandler}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/TagFilterBar.jsx",
    "content": "import {Button, Col, Form, Input, Row, Select, Space} from \"antd\";\nimport {useDispatch} from \"umi\";\nimport {filterFormProp} from \"@/utils/var\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const [form] = Form.useForm();\n  const dispatch = useDispatch();\n\n  async function resetHandler() {\n    form.resetFields();\n    const values = await form.validateFields();\n    dispatch({ type: 'categories/updateState', payload: {tagQueryData: values} });\n  }\n\n  async function searchHandler(form) {\n    const values = await form.validateFields();\n    dispatch({ type: 'categories/queryTag', payload: values });\n    dispatch({ type: 'categories/updateState', payload: {tagQueryData: values} });\n  }\n\n  return (\n    <Form {...filterFormProp} form={form} size=\"small\">\n      <Row gutter={20}>\n        <Col flex=\"300px\">\n          <Form.Item label={t('name')} name=\"name\">\n            <Input allowClear={true} />\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('is.enable')} name=\"enable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('expenseable')} name=\"expenseable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('incomeable')} name=\"incomeable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n        <Col flex=\"200px\">\n          <Form.Item label={t('transferable')} name=\"transferable\">\n            <Select allowClear={true}>\n              <Select.Option value={true}>{t('yes')}</Select.Option>\n              <Select.Option value={false}>{t('no')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n      </Row>\n      <Row gutter={20} justify=\"end\">\n        <Col span={24} style={{textAlign: \"right\"}}>\n          <Space>\n            <Button type=\"primary\" size=\"small\" onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" size=\"small\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/TagModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Input, message, Switch, TreeSelect} from 'antd';\nimport { create, update } from '@/services/tag';\nimport FormModal from \"@/components/FormModal\";\nimport {nameRules, notesRules} from \"@/utils/rules\";\nimport {getNull, tableChangeQueryFormat, validateForm} from \"@/utils/util\";\nimport {useCategoryTreeSelectData} from \"@/utils/hooks\";\nimport t from \"@/utils/translate\";\nimport styles from './index.less';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const { getEnableResponse : tagResponse } = useSelector(state => state.tag);\n  const [treeData] = useCategoryTreeSelectData(tagResponse);\n  useEffect(() => {\n    if (visible) {\n      if (!tagResponse) dispatch({ type: 'tag/fetchEnable' });\n    }\n  }, [visible]);\n\n  const [initialValues, setInitialValues] = useState({})\n  useEffect(() => {\n    if (visible) {\n      if (type === 1) {\n        setInitialValues({\n          ...getNull(form.getFieldsValue()),\n          ...{\n            expenseable: currentItem && currentItem.expenseable ? currentItem.expenseable : true,\n            incomeable: currentItem && currentItem.incomeable ? currentItem.incomeable : false,\n            transferable: currentItem && currentItem.transferable ? currentItem.transferable :false,\n            parentId: currentItem && currentItem.id ? currentItem.id : null,\n          }\n        });\n      } else {\n        setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n      }\n    }\n  }, [visible]);\n\n  function successHandler(response) {\n    dispatch({ type: 'categories/queryTag' });\n    dispatch({ type: 'tag/refresh', payload: { ...response.data } });\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('tag')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={(response) => successHandler(response)}\n    >\n      <Form form={form} className={styles['form']}>\n        <Form.Item label={t('parent.category')} name=\"parentId\">\n          <TreeSelect\n            allowClear={true}\n            treeDataSimpleMode={true}\n            treeData={treeData}\n            showArrow={true}\n            showSearch={true}\n            treeNodeFilterProp=\"title\"\n            treeDefaultExpandAll={false} />\n        </Form.Item>\n        <Form.Item label={t('name')} name=\"name\" rules={nameRules()}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('expenseable')} valuePropName=\"checked\" name=\"expenseable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('incomeable')} valuePropName=\"checked\" name=\"incomeable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('transferable')} valuePropName=\"checked\" name=\"transferable\">\n          <Switch />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/TagTable.jsx",
    "content": "import {useEffect} from \"react\";\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport {Button, message, Table, Space, Modal, Switch} from \"antd\";\nimport {tableProp} from \"@/utils/var\";\nimport {searchTreeArray, tableChangeQueryFormat} from \"@/utils/util\";\nimport { categoryExpenseableCol, categoryIncomeableCol, categoryTransferableCol } from '@/utils/columns';\nimport { remove, toggle } from '@/services/tag';\nimport {useResponseData} from '@/utils/hooks';\nimport TagModal from './TagModal';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryTagResponse : queryResponse } = useSelector(state => state.categories);\n  const queryLoading = useSelector(state => state.loading.effects['categories/queryTag']);\n  const [tagTreeData, setTagTreeData] = useResponseData(queryResponse);\n\n  useEffect(() => {\n    dispatch({ type: 'categories/queryTag' });\n  }, []);\n\n  function refresh() {\n    dispatch({ type: 'categories/queryTag' });\n    dispatch({ type: 'tag/refresh'});\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const toggleHandler = async (record) => {\n    // 乐观更新\n    let newTreeData = JSON.parse(JSON.stringify(tagTreeData));\n    let node = searchTreeArray(newTreeData, record.id);\n    if (node) node.enable = !node.enable;\n    setTagTreeData(newTreeData);\n    const response = await toggle(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      refresh();\n    }\n  }\n\n  const addHandler = (record, event) => {\n    dispatch({ type: 'modal/show', payload: {component: TagModal, type: 1, currentItem: record }});\n    event.stopPropagation();\n  }\n\n  const updateHandler = (record, event) => {\n    dispatch({ type: 'modal/show', payload: {component: TagModal, type: 2, currentItem: record }});\n    event.stopPropagation();\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record, event) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          refresh();\n        }\n      }\n    });\n    event.stopPropagation();\n  }\n\n  const columns = [\n    {\n      title: t('name'),\n      dataIndex: 'name',\n    },\n    {\n      title: t('notes'),\n      dataIndex: 'notes',\n    },\n    {\n      title: t('is.enable'),\n      dataIndex: 'enable',\n      width: 150,\n      sorter: true,\n      render: (value, record) => <Switch onClick = { (_, e) => e.stopPropagation() } onChange={() => toggleHandler(record)} checked={value} />\n    },\n    categoryExpenseableCol(),\n    categoryIncomeableCol(),\n    categoryTransferableCol(),\n    {\n      title: t('operation'),\n      key: 'operation',\n      width: 160,\n      align: 'center',\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={(event) => updateHandler(record, event)}>{t('update')}</Button>\n            <Button size=\"small\" disabled={!record.enable} onClick={(event) => addHandler(record, event)}>{t('new')}</Button>\n            <Button size=\"small\" onClick={(event) => deleteHandler(record, event)}>{t('delete')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    tagTreeData && tagTreeData.length > 0 && <Table\n      {...tableProp}\n      size=\"middle\"\n      columns={columns}\n      dataSource={tagTreeData}\n      loading={queryLoading}\n      expandable={{ expandRowByClick: true, defaultExpandAllRows: false }}\n      pagination={false}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/index.jsx",
    "content": "import { useSelector } from 'umi';\nimport { Space } from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from '@/components/Breadcrumb';\nimport OperationBar from './OperationBar';\nimport ExpenseCategoryTable from './ExpenseCategoryTable';\nimport IncomeCategoryTable from './IncomeCategoryTable';\nimport TagTable from './TagTable';\nimport PayeeTable from './PayeeTable';\nimport PayeeFilterBar from \"./PayeeFilterBar\";\nimport TagFilterBar from \"./TagFilterBar\";\nimport ExpenseCategoryFilterBar from \"./ExpenseCategoryFilterBar\";\nimport IncomeCategoryFilterBar from \"./IncomeCategoryFilterBar\";\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const { currentCategoryType } = useSelector(state => state.categories);\n\n  const tableSwitch = (type) => {\n    switch(type) {\n      case 1:\n        return <ExpenseCategoryTable />;\n      case 2:\n        return <IncomeCategoryTable />;\n      case 3:\n        return <TagTable />;\n      case 4:\n        return <PayeeTable />;\n    }\n  }\n\n  const filterSwitch = (type) => {\n    switch(type) {\n      case 1:\n        return <ExpenseCategoryFilterBar />;\n      case 2:\n        return <IncomeCategoryFilterBar />;\n      case 3:\n        return <TagFilterBar />;\n      case 4:\n        return <PayeeFilterBar />;\n    }\n  }\n\n  return (\n    <Space  {...spaceVProp}>\n      <Breadcrumb data={[t('menu.settings'), t('menu.category')]} />\n      <OperationBar />\n      { filterSwitch(currentCategoryType) }\n      { tableSwitch(currentCategoryType) }\n    </Space>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/index.less",
    "content": ".form {\n  :global {\n    .ant-form-item-label {\n      width: 65px;\n    }\n  }\n}\n.form2 {\n  :global {\n    .ant-form-item-label {\n      width: 45px;\n    }\n  }\n}\n.form3 {\n  :global {\n    .ant-form-item-label {\n      width: 50px;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/categories/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport { model } from '@/utils/model';\nimport { query as queryExpenseCategory } from '@/services/expense-category';\nimport { query as queryIncomeCategory } from '@/services/income-category';\nimport { query as queryTag } from '@/services/tag';\nimport { query as queryPayee } from '@/services/payee';\n\nexport default modelExtend(model, {\n  namespace: 'categories',\n  state: {\n    currentCategoryType: 1,\n\n    queryExpenseCategoryResponse: undefined,\n    queryIncomeCategoryResponse: undefined,\n    queryTagResponse: undefined,\n    queryPayeeResponse: undefined,\n\n    payeePagination: { },\n    payeeSorter: { },\n    payeeQueryData: { },\n\n    expenseCategoryQueryData: { },\n    incomeCategoryQueryData: { },\n    tagQueryData: { },\n\n  },\n  effects: {\n    *queryExpenseCategory({ payload }, { call, put }) {\n      const response = yield call(queryExpenseCategory, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryExpenseCategoryResponse: response },\n      });\n    },\n    *queryIncomeCategory({ payload }, { call, put }) {\n      const response = yield call(queryIncomeCategory, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryIncomeCategoryResponse: response },\n      });\n    },\n    *queryTag({ payload }, { call, put }) {\n      const response = yield call(queryTag, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryTagResponse: response },\n      });\n    },\n    *queryPayee({ payload }, { call, put }) {\n      const response = yield call(queryPayee, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryPayeeResponse: response },\n      });\n    },\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/checking-accounts/GeneralBar.jsx",
    "content": "import { useEffect } from 'react';\nimport {Button, Card, Col, Row, Statistic} from \"antd\";\nimport {ReloadOutlined} from \"@ant-design/icons\";\nimport { useDispatch, useSelector } from 'umi';\nimport { useResponseData } from '@/utils/hooks';\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'checkingAccounts/sum' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['checkingAccounts/sum']);\n  const queryLoading = useSelector(state => state.loading.effects['checkingAccounts/query']);\n  const { sumResponse } = useSelector(state => state.checkingAccounts);\n  const [sum] = useResponseData(sumResponse);\n\n  function refreshHandler() {\n    dispatch({ type: 'checkingAccounts/refresh' });\n  }\n\n  return (\n    <Row gutter={16} align=\"middle\">\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('gross.balance')} value={sum.balance} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col flex=\"auto\" style={{ textAlign: \"right\" }}>\n        <Button type=\"primary\" size=\"large\" loading={queryLoading || loading} icon={<ReloadOutlined />} onClick={ refreshHandler }>{t('reload')}</Button>\n      </Col>\n    </Row>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/checking-accounts/ItemCard.jsx",
    "content": "import {useSelector} from \"umi\";\nimport {Card, Row, Col, Button, Space, Descriptions, Collapse} from 'antd';\nimport { spaceVProp }  from '@/utils/var';\nimport AccountRecordTable from '@/components/AccountRecordTable';\nimport {showFlowModal} from '@/utils/flow';\nimport styles from './index.less';\nimport t from '@/utils/translate';\n\n\nexport default (props) => {\n\n  const { account } = props;\n  const { currentTime } = useSelector(state => state.checkingAccounts);\n\n  function expenseHandler() {\n    showFlowModal(1, 1, {accountId: account.id});\n  }\n\n  function incomeHandler() {\n    showFlowModal(2, 1, {accountId: account.id});\n  }\n\n  function transferToHandler() {\n    showFlowModal(3, 1, { toId: account.id });\n  }\n\n  function transferFromHandler() {\n    showFlowModal(3, 1, { fromId: account.id });\n  }\n\n  function adjustBalanceHandler() {\n    showFlowModal(4, 2, { ...account });\n  }\n\n  return (\n    <Row gutter={16}>\n      <Col span={24}>\n          <Card title={account.name} bordered={false} size=\"small\">\n            <Space {...spaceVProp}>\n              <Descriptions bordered={true} size=\"small\" column={3}>\n                <Descriptions.Item label={t('account.balance')}>{account.balance}</Descriptions.Item>\n                <Descriptions.Item label={t('currency')}>{account.currencyCode}</Descriptions.Item>\n                <Descriptions.Item label={t('account.include')}>{account.include ? t('yes') : t('no')}</Descriptions.Item>\n              </Descriptions>\n              <Space size=\"small\">\n                <Button disabled={!account.expenseable} size=\"small\" type=\"primary\" onClick={expenseHandler}>{t('expense')}</Button>\n                <Button disabled={!account.incomeable} size=\"small\" type=\"primary\" onClick={incomeHandler}>{t('income')}</Button>\n                <Button disabled={!account.transferToAble} size=\"small\" type=\"primary\" onClick={transferToHandler}>{t('transfer.to')}</Button>\n                <Button disabled={!account.transferFromAble} size=\"small\" type=\"primary\" onClick={transferFromHandler}>{t('transfer.from')}</Button>\n                <Button size=\"small\" type=\"primary\" onClick={adjustBalanceHandler}>{t('adjust.balance')}</Button>\n              </Space>\n              <Collapse className={styles['item-card-collapse']}>\n                <Collapse.Panel key={1} header={t('flow.record')}>\n                  <AccountRecordTable accountId={account.id} currentTime={currentTime} />\n                </Collapse.Panel>\n              </Collapse>\n            </Space>\n          </Card>\n      </Col>\n    </Row>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/checking-accounts/List.jsx",
    "content": "import { useEffect } from \"react\";\nimport {Empty, Pagination, Space, Spin} from \"antd\";\nimport { useDispatch, useSelector } from \"umi\";\nimport {spaceVProp} from \"@/utils/var\";\nimport { usePaginationAndData } from \"@/utils/hooks\";\nimport { paginationChange } from \"@/utils/util\";\nimport ItemCard from \"./ItemCard\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryResponse } = useSelector(state => state.checkingAccounts);\n  const queryLoading = useSelector(state => state.loading.effects['checkingAccounts/query']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n\n  return (\n    <Spin spinning={queryLoading} size=\"large\">\n      { dataAndPagination.data && dataAndPagination.data.length > 0 ?\n        (\n          <Space {...spaceVProp}>\n            { dataAndPagination.data.map(item => <ItemCard key={item.id} account={item} />) }\n            <Pagination {...dataAndPagination.pagination} onChange={paginationChange} />\n          </Space>\n        ) :\n        (<Empty style={{ marginTop: 50 }} />)\n      }\n    </Spin>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/checking-accounts/index.jsx",
    "content": "import { Space } from 'antd';\nimport { spaceVProp }  from '@/utils/var';\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport GeneralBar from './GeneralBar';\nimport List from './List';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.account.list'), t('menu.checking.account')]} />\n      <GeneralBar />\n      <List />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/checking-accounts/index.less",
    "content": ".item-card-collapse {\n  :global {\n    .ant-collapse-content-box {\n      padding: 0 !important;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/checking-accounts/model.js",
    "content": "import {history} from \"umi\";\nimport modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport {query, sum} from \"@/services/checking-account\";\n\nexport default modelExtend(model, {\n  namespace: 'checkingAccounts',\n  state: {\n    queryResponse: undefined,\n    sumResponse: undefined,\n    currentTime: Date.now()\n  },\n  effects: {\n    *sum(_, { call, put }) {\n      const response = yield call(sum);\n      yield put({\n        type: 'updateState',\n        payload: { sumResponse: response },\n      });\n    },\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, { ...payload, ...{ enable: true } });\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n    *refresh(_, { call, put, select }) {\n      yield put({ type: 'query', payload: history.location.query });\n      yield put({ type: 'sum' });\n      // 更新账户的流水记录\n      yield put({ type: 'updateState', payload: {currentTime: new Date()} });\n    }\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/checking-accounts') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/credit-accounts/GeneralBar.jsx",
    "content": "import { useEffect } from 'react';\nimport {Button, Card, Col, Row, Statistic} from \"antd\";\nimport {ReloadOutlined} from \"@ant-design/icons\";\nimport { useDispatch, useSelector } from 'umi';\nimport { useResponseData } from '@/utils/hooks';\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'creditAccounts/sum' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['creditAccounts/sum']);\n  const queryLoading = useSelector(state => state.loading.effects['creditAccounts/query']);\n  const { sumResponse } = useSelector(state => state.creditAccounts);\n  const [sum] = useResponseData(sumResponse);\n\n  function refreshHandler() {\n    dispatch({ type: 'creditAccounts/refresh' });\n  }\n\n  return (\n    <Row gutter={16} align=\"middle\">\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('gross.balance')} value={sum.balance} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('gross.limit')} value={sum.limit} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('gross.remain.limit')} value={sum.remainLimit} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col flex=\"auto\" style={{ textAlign: \"right\" }}>\n        <Button type=\"primary\" size=\"large\" loading={queryLoading || loading} icon={<ReloadOutlined />} onClick={ refreshHandler }>{t('reload')}</Button>\n      </Col>\n    </Row>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/credit-accounts/ItemCard.jsx",
    "content": "import {useSelector} from \"umi\";\nimport {Card, Row, Col, Button, Space, Descriptions, Collapse} from 'antd';\nimport { spaceVProp }  from '@/utils/var';\nimport AccountRecordTable from '@/components/AccountRecordTable';\nimport {showFlowModal} from \"@/utils/flow\";\nimport styles from './index.less';\nimport t from '@/utils/translate';\n\n\nexport default (props) => {\n\n  const { account } = props;\n  const { currentTime } = useSelector(state => state.creditAccounts);\n\n  function expenseHandler() {\n    showFlowModal(1, 1, {accountId: account.id});\n  }\n\n  function incomeHandler() {\n    showFlowModal(2, 1, {accountId: account.id});\n  }\n\n  function transferToHandler() {\n    showFlowModal(3, 1, { toId: account.id });\n  }\n\n  function transferFromHandler() {\n    showFlowModal(3, 1, { fromId: account.id });\n  }\n\n  function adjustBalanceHandler() {\n    showFlowModal(4, 2, { ...account });\n  }\n\n  return (\n    <Row gutter={16}>\n      <Col span={24}>\n          <Card title={account.name} bordered={false} size=\"small\">\n            <Space {...spaceVProp}>\n              <Descriptions bordered={true} size=\"small\" column={5}>\n                <Descriptions.Item label={t('account.balance')}>{account.balance}</Descriptions.Item>\n                <Descriptions.Item label={t('currency')}>{account.currencyCode}</Descriptions.Item>\n                <Descriptions.Item label={t('credit.limit')}>{account.limit}</Descriptions.Item>\n                <Descriptions.Item label={t('remain.limit')}>{account.remainLimit}</Descriptions.Item>\n                <Descriptions.Item label={t('account.include')}>{account.include ? t('yes') : t('no')}</Descriptions.Item>\n              </Descriptions>\n              <Space size=\"small\">\n                <Button disabled={!account.expenseable} size=\"small\" type=\"primary\" onClick={expenseHandler}>{t('expense')}</Button>\n                <Button disabled={!account.incomeable} size=\"small\" type=\"primary\" onClick={incomeHandler}>{t('income')}</Button>\n                <Button disabled={!account.transferToAble} size=\"small\" type=\"primary\" onClick={transferToHandler}>{t('transfer.to')}</Button>\n                <Button disabled={!account.transferFromAble} size=\"small\" type=\"primary\" onClick={transferFromHandler}>{t('transfer.from')}</Button>\n                <Button size=\"small\" type=\"primary\" onClick={adjustBalanceHandler}>{t('adjust.balance')}</Button>\n              </Space>\n              <Collapse className={styles['item-card-collapse']}>\n                <Collapse.Panel key={1} header={t('flow.record')}>\n                  <AccountRecordTable accountId={account.id} currentTime={currentTime} />\n                </Collapse.Panel>\n              </Collapse>\n            </Space>\n          </Card>\n      </Col>\n    </Row>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/credit-accounts/List.jsx",
    "content": "import { useEffect } from \"react\";\nimport {Empty, Pagination, Space, Spin} from \"antd\";\nimport { useDispatch, useSelector } from \"umi\";\nimport { usePaginationAndData } from \"@/utils/hooks\";\nimport {spaceVProp} from \"@/utils/var\";\nimport {paginationChange} from \"@/utils/util\";\nimport ItemCard from \"./ItemCard\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryResponse } = useSelector(state => state.creditAccounts);\n  const queryLoading = useSelector(state => state.loading.effects['creditAccounts/query']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n\n  return (\n    <Spin spinning={queryLoading} size=\"large\">\n      { dataAndPagination.data && dataAndPagination.data.length > 0 ?\n        (\n          <Space {...spaceVProp}>\n            { dataAndPagination.data.map(item => <ItemCard key={item.id} account={item} />) }\n            <Pagination {...dataAndPagination.pagination} onChange={paginationChange} />\n          </Space>\n        ) :\n        (<Empty style={{ marginTop: 50 }} />)\n      }\n    </Spin>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/credit-accounts/index.jsx",
    "content": "import { Space } from 'antd';\nimport { spaceVProp }  from '@/utils/var';\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport GeneralBar from './GeneralBar';\nimport List from './List';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.account.list'), t('menu.credit.account')]} />\n      <GeneralBar />\n      <List />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/credit-accounts/index.less",
    "content": ".item-card-collapse {\n  :global {\n    .ant-collapse-content-box {\n      padding: 0 !important;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/credit-accounts/model.js",
    "content": "import {history} from \"umi\";\nimport modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport {query, sum} from \"@/services/credit-account\";\n\nexport default modelExtend(model, {\n  namespace: 'creditAccounts',\n  state: {\n    queryResponse: undefined,\n    sumResponse: undefined,\n    currentTime: Date.now()\n  },\n  effects: {\n    *sum(_, { call, put }) {\n      const response = yield call(sum);\n      yield put({\n        type: 'updateState',\n        payload: { sumResponse: response },\n      });\n    },\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, { ...payload, ...{ enable: true } });\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n    *refresh(_, { call, put, select }) {\n      yield put({ type: 'query', payload: history.location.query });\n      yield put({ type: 'sum' });\n      // 更新账户的流水记录\n      yield put({ type: 'updateState', payload: {currentTime: new Date()} });\n    }\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/credit-accounts') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/AssetBar.jsx",
    "content": "import {useEffect} from \"react\";\nimport {Button, Card, Col, Row, Statistic} from \"antd\";\nimport {useDispatch, useSelector} from \"umi\";\nimport t from '@/utils/translate';\nimport {useResponseData} from \"@/utils/hooks\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'dashboard/fetchAssetOverview' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['dashboard/fetchAssetOverview']);\n  const { getAssetOverviewResponse } = useSelector(state => state.dashboard);\n  const [assetOverview] = useResponseData(getAssetOverviewResponse);\n\n  return (\n    <Row gutter={8} align=\"middle\">\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('asset')} value={assetOverview.asset} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('debt')} value={assetOverview.debt} valueStyle={{ color: \"#f1523a\" }} />\n        </Card>\n      </Col>\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('net.worth')} value={assetOverview.netWorth} valueStyle={{ color: \"#14ba89\" }} />\n        </Card>\n      </Col>\n    </Row>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/CardExtra.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {DatePicker, Radio, Space} from \"antd\";\nimport {radioValueToTimeRange} from '@/utils/util';\nimport t from '@/utils/translate';\n\n\nexport default (props) => {\n\n  const { value, onChange } = props;\n\n  const [ rangePickerValue, setRangePickerValue ] = useState([]);\n\n  useEffect(() => {\n    setRangePickerValue(radioValueToTimeRange(value));\n  }, [value]);\n\n  function radioChangeHandler(e) {\n    onChange(e.target.value);\n  }\n\n  return <Space>\n    <Radio.Group value={value} onChange={radioChangeHandler}>\n      <Radio.Button value={7}>{t('in.30.days')}</Radio.Button>\n      <Radio.Button value={8}>{t('in.1.year')}</Radio.Button>\n      <Radio.Button value={3}>{t('this.month')}</Radio.Button>\n      <Radio.Button value={4}>{t('this.year')}</Radio.Button>\n      <Radio.Button value={5}>{t('last.year')}</Radio.Button>\n    </Radio.Group>\n    <DatePicker.RangePicker disabled={true} allowClear={false} value={rangePickerValue} showTime={false} format=\"YYYY-MM-DD\" />\n  </Space>\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/ExpenseCategory.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card } from \"antd\";\nimport CardExtra from './CardExtra';\nimport {radioValueToTimeRange} from \"@/utils/util\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport Pie from '@/components/charts/Pie';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const [timeRange, setTimeRange] = useState(7);\n  function timeRangeChangeHanlder(value) {\n    setTimeRange(value);\n  }\n\n  useEffect(() => {\n    const rangeValues = radioValueToTimeRange(timeRange);\n    dispatch({\n      type: 'dashboard/fetchExpenseCategory',\n      payload: { start: rangeValues[0].valueOf(), end: rangeValues[1].valueOf() }\n    });\n  }, [timeRange]);\n  const loading = useSelector(state => state.loading.effects['dashboard/fetchExpenseCategory']);\n  const { getExpenseCategoryResponse } = useSelector(state => state.dashboard);\n  const [data] = useResponseData(getExpenseCategoryResponse);\n\n  return (\n    <Card title={t('expense.category')} bordered={false} bodyStyle={{padding: '40px 10px'}} extra={<CardExtra value={timeRange} onChange={timeRangeChangeHanlder} />}>\n      <Pie data={data} loading={loading} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/ExpenseTrend.jsx",
    "content": "import { useEffect } from \"react\";\nimport { useDispatch, useSelector } from \"umi\";\nimport { useResponseData } from \"@/utils/hooks\";\nimport { Card, Tabs } from \"antd\";\nimport Bar from '@/components/charts/Bar';\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'dashboard/fetchExpenseTrend' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['dashboard/fetchExpenseTrend']);\n  const { getExpenseTrendResponse } = useSelector(state => state.dashboard);\n  const [data] = useResponseData(getExpenseTrendResponse);\n\n  return (\n    <Card title={t('expense.trend')} bordered={false}>\n      <Bar data={data} loading={loading} />\n    </Card>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/IncomeCategory.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card } from \"antd\";\nimport CardExtra from './CardExtra';\nimport {radioValueToTimeRange} from \"@/utils/util\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport Pie from '@/components/charts/Pie';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const [timeRange, setTimeRange] = useState(7);\n  function timeRangeChangeHanlder(value) {\n    setTimeRange(value);\n  }\n\n  useEffect(() => {\n    const rangeValues = radioValueToTimeRange(timeRange);\n    dispatch({\n      type: 'dashboard/fetchIncomeCategory',\n      payload: { start: rangeValues[0].valueOf(), end: rangeValues[1].valueOf() }\n    });\n  }, [timeRange]);\n  const loading = useSelector(state => state.loading.effects['dashboard/fetchIncomeCategory']);\n  const { getIncomeCategoryResponse } = useSelector(state => state.dashboard);\n  const [data] = useResponseData(getIncomeCategoryResponse);\n\n  return (\n    <Card title={t('income.category')} bordered={false} bodyStyle={{padding: '40px 10px'}} extra={<CardExtra value={timeRange} onChange={timeRangeChangeHanlder} />}>\n      <Pie data={data} loading={loading} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/IncomeTrend.jsx",
    "content": "import { useEffect } from \"react\";\nimport { useDispatch, useSelector } from \"umi\";\nimport { useResponseData } from \"@/utils/hooks\";\nimport { Card, Tabs } from \"antd\";\nimport Bar from '@/components/charts/Bar';\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'dashboard/fetchIncomeTrend' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['dashboard/fetchIncomeTrend']);\n  const { getIncomeTrendResponse } = useSelector(state => state.dashboard);\n  const [data] = useResponseData(getIncomeTrendResponse);\n\n  return (\n    <Card title={t('income.trend')} bordered={false}>\n      <Bar data={data} loading={loading} />\n    </Card>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/TransactionTable.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card, Table } from 'antd';\nimport styles from './TransactionTable.less';\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'dashboard/fetchExpenseIncomeTable' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['dashboard/fetchExpenseIncomeTable']);\n  const { getExpenseIncomeTableResponse } = useSelector(state => state.dashboard);\n  const [data, setData] = useState([]);\n  const expenseMessage = t('expense') + t('amount');\n  const incomeMessage = t('income') + t('amount');\n  const frequencyExpenseMessage = t('frequency.expense');\n  const frequencyIncomeMessage = t('frequency.income');\n  useEffect(() => {\n    if (!getExpenseIncomeTableResponse)  return;\n    if (getExpenseIncomeTableResponse.success) {\n      const responseData = getExpenseIncomeTableResponse.data;\n      setData(\n        [\n          {\n            key: '1',\n            name: expenseMessage,\n            week: responseData[0][0],\n            month: responseData[0][1],\n            year: responseData[0][2],\n            year2: responseData[0][3],\n            week2: responseData[0][4],\n            month2: responseData[0][5],\n            year3: responseData[0][6],\n          },\n          {\n            key: '2',\n            name: incomeMessage,\n            week: responseData[1][0],\n            month: responseData[1][1],\n            year: responseData[1][2],\n            year2: responseData[1][3],\n            week2: responseData[1][4],\n            month2: responseData[1][5],\n            year3: responseData[1][6],\n          },\n          {\n            key: '3',\n            name: frequencyExpenseMessage,\n            week: responseData[2][0],\n            month: responseData[2][1],\n            year: responseData[2][2],\n            year2: responseData[2][3],\n            week2: responseData[2][4],\n            month2: responseData[2][5],\n            year3: responseData[2][6],\n          },\n          {\n            key: '4',\n            name: frequencyIncomeMessage,\n            week: responseData[3][0],\n            month: responseData[3][1],\n            year: responseData[3][2],\n            year2: responseData[3][3],\n            week2: responseData[3][4],\n            month2: responseData[3][5],\n            year3: responseData[3][6],\n          },\n        ]\n      );\n    }\n  }, [getExpenseIncomeTableResponse]);\n\n  const columns = [\n    {\n      title: '',\n      dataIndex: 'name',\n      width: 150,\n      className: 'name-col'\n    },\n    {\n      title: t('this.week'),\n      dataIndex: 'week',\n    },\n    {\n      title: t('this.month'),\n      dataIndex: 'month',\n    },\n    {\n      title: t('this.year'),\n      dataIndex: 'year',\n    },\n    {\n      title: t('last.year'),\n      dataIndex: 'year2',\n    },\n    {\n      title: t('in.7.days'),\n      dataIndex: 'week2',\n    },\n    {\n      title: t('in.30.days'),\n      dataIndex: 'month2',\n    },\n    {\n      title: t('in.1.year'),\n      dataIndex: 'year3',\n    }\n  ];\n\n  return (\n    <Card title={t('income.expense.table')} bordered={false} bodyStyle={{ padding: 0 }}>\n      <Table\n        loading={loading}\n        size=\"small\"\n        className={styles['table']}\n        columns={columns}\n        dataSource={data}\n        pagination={false} />\n    </Card>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/TransactionTable.less",
    "content": ".table {\n  :global {\n    .name-col {\n      background: #fafafa;\n    }\n    tr:hover > td {\n      background-color: transparent !important;\n    }\n    tr:hover > td.name-col {\n      background-color: #fafafa !important;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/index.jsx",
    "content": "import {Col, Row, Space} from \"antd\";\nimport { spaceVProp } from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport AssetBar from './AssetBar';\nimport TransactionTable from './TransactionTable';\nimport ExpenseTrend from './ExpenseTrend';\nimport IncomeTrend from './IncomeTrend';\nimport ExpenseCategory from './ExpenseCategory';\nimport IncomeCategory from './IncomeCategory';\nimport styles from './index.less';\nimport t from '@/utils/translate';\n\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.report'), t('menu.overview')]} />\n      <AssetBar />\n      <TransactionTable />\n      <Row gutter={8} className={styles['pie-row']}>\n        <Col span={12}>\n          <ExpenseCategory />\n        </Col>\n        <Col span={12}>\n          <IncomeCategory />\n        </Col>\n      </Row>\n      <Row gutter={8}>\n        <Col span={24}>\n          <ExpenseTrend />\n        </Col>\n      </Row>\n      <Row gutter={8}>\n        <Col span={24}>\n          <IncomeTrend />\n        </Col>\n      </Row>\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/index.less",
    "content": ".pie-row {\n  :global(.ant-card) {\n    height: 100%;\n  }\n}\n.trend {\n  display: flex;\n  flex-flow: row;\n  align-items: center;\n  >div {\n    flex: 1;\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/dashboard/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getAssetOverview, getExpenseIncomeTable, getExpenseTrend, getIncomeTrend, getExpenseCategory, getIncomeCategory } from '@/services/dashboard';\n\nexport default modelExtend(model, {\n  namespace: 'dashboard',\n  state: {\n    getAssetOverviewResponse: undefined,\n    getExpenseIncomeTableResponse: undefined,\n    getExpenseTrendResponse: undefined,\n    getIncomeTrendResponse: undefined,\n    getExpenseCategoryResponse: undefined,\n    getIncomeCategoryResponse: undefined,\n  },\n  effects: {\n    *fetchAssetOverview(_, { call, put }) {\n      const response = yield call(getAssetOverview);\n      yield put({\n        type: 'updateState',\n        payload: { getAssetOverviewResponse: response },\n      });\n    },\n    *fetchExpenseIncomeTable(_, { call, put }) {\n      const response = yield call(getExpenseIncomeTable);\n      yield put({\n        type: 'updateState',\n        payload: { getExpenseIncomeTableResponse: response },\n      });\n    },\n    *fetchExpenseTrend(_, { call, put }) {\n      const response = yield call(getExpenseTrend);\n      yield put({\n        type: 'updateState',\n        payload: { getExpenseTrendResponse: response },\n      });\n    },\n    *fetchIncomeTrend(_, { call, put }) {\n      const response = yield call(getIncomeTrend);\n      yield put({\n        type: 'updateState',\n        payload: { getIncomeTrendResponse: response },\n      });\n    },\n    *fetchExpenseCategory({ payload }, { call, put }) {\n      const response = yield call(getExpenseCategory, payload);\n      yield put({\n        type: 'updateState',\n        payload: { getExpenseCategoryResponse: response },\n      });\n    },\n    *fetchIncomeCategory({ payload }, { call, put }) {\n      const response = yield call(getIncomeCategory, payload);\n      yield put({\n        type: 'updateState',\n        payload: { getIncomeCategoryResponse: response },\n      });\n    },\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/debt-accounts/GeneralBar.jsx",
    "content": "import { useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Button, Card, Col, Row, Statistic} from \"antd\";\nimport {ReloadOutlined} from \"@ant-design/icons\";\nimport { useResponseData } from '@/utils/hooks';\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'debtAccounts/sum' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['debtAccounts/sum']);\n  const queryLoading = useSelector(state => state.loading.effects['debtAccounts/query']);\n  const { sumResponse } = useSelector(state => state.debtAccounts);\n  const [sum] = useResponseData(sumResponse);\n\n  function refreshHandler() {\n    dispatch({ type: 'debtAccounts/refresh' });\n  }\n\n  return (\n    <Row gutter={16} align=\"middle\">\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('gross.balance')} value={sum.balance} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('gross.limit')} value={sum.limit} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col span={4}>\n        <Card bordered={false} size=\"small\">\n          <Statistic loading={loading} title={t('gross.remain.limit')} value={sum.remainLimit} valueStyle={{ color: \"#2e2e2e\" }} />\n        </Card>\n      </Col>\n      <Col flex=\"auto\" style={{ textAlign: \"right\" }}>\n        <Button type=\"primary\" size=\"large\" loading={queryLoading || loading} icon={<ReloadOutlined />} onClick={ refreshHandler }>{t('reload')}</Button>\n      </Col>\n    </Row>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/debt-accounts/ItemCard.jsx",
    "content": "import {useSelector} from \"umi\";\nimport {Card, Row, Col, Button, Space, Descriptions, Collapse} from 'antd';\nimport { spaceVProp }  from '@/utils/var';\nimport {showFlowModal} from \"@/utils/flow\";\nimport AccountRecordTable from \"@/components/AccountRecordTable\";\nimport styles from './index.less';\nimport t from \"@/utils/translate\";\n\nexport default (props) => {\n\n  const { account } = props;\n  const { currentTime } = useSelector(state => state.debtAccounts);\n\n  function expenseHandler() {\n    showFlowModal(1, 1, {accountId: account.id});\n  }\n\n  function incomeHandler() {\n    showFlowModal(2, 1, {accountId: account.id});\n  }\n\n  function transferToHandler() {\n    showFlowModal(3, 1, { toId: account.id });\n  }\n\n  function transferFromHandler() {\n    showFlowModal(3, 1, { fromId: account.id });\n  }\n\n  function adjustBalanceHandler() {\n    showFlowModal(4, 2, { ...account });\n  }\n\n  return (\n    <Row gutter={16}>\n      <Col span={24}>\n          <Card title={account.name} bordered={false} size=\"small\">\n            <Space {...spaceVProp}>\n              <Descriptions bordered={true} size=\"small\" column={5}>\n                <Descriptions.Item label={t('account.balance')}>{account.balance}</Descriptions.Item>\n                <Descriptions.Item label={t('currency')}>{account.currencyCode}</Descriptions.Item>\n                <Descriptions.Item label={t('debt.limit')}>{account.limit ? account.limit : ''}</Descriptions.Item>\n                <Descriptions.Item label={t('remain.limit')}>{account.remainLimit ? account.remainLimit : ''}</Descriptions.Item>\n                <Descriptions.Item label={t('account.include')}>{account.include ? t('yes') : t('no')}</Descriptions.Item>\n              </Descriptions>\n              <Space size=\"small\">\n                <Button disabled={!account.expenseable} size=\"small\" type=\"primary\" onClick={expenseHandler}>{t('expense')}</Button>\n                <Button disabled={!account.incomeable} size=\"small\" type=\"primary\" onClick={incomeHandler}>{t('income')}</Button>\n                <Button disabled={!account.transferToAble} size=\"small\" type=\"primary\" onClick={transferToHandler}>{t('transfer.to')}</Button>\n                <Button disabled={!account.transferFromAble} size=\"small\" type=\"primary\" onClick={transferFromHandler}>{t('transfer.from')}</Button>\n                <Button size=\"small\" type=\"primary\" onClick={adjustBalanceHandler}>{t('adjust.balance')}</Button>\n              </Space>\n              <Collapse className={styles['item-card-collapse']}>\n                <Collapse.Panel key={1} header={t('flow.record')}>\n                  <AccountRecordTable accountId={account.id} currentTime={currentTime} />\n                </Collapse.Panel>\n              </Collapse>\n            </Space>\n          </Card>\n      </Col>\n    </Row>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/debt-accounts/List.jsx",
    "content": "import { useEffect } from \"react\";\nimport {Empty, Pagination, Space, Spin} from \"antd\";\nimport { useDispatch, useSelector } from \"umi\";\nimport { usePaginationAndData } from \"@/utils/hooks\";\nimport {spaceVProp} from \"@/utils/var\";\nimport {paginationChange} from \"@/utils/util\";\nimport ItemCard from \"./ItemCard\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryResponse } = useSelector(state => state.debtAccounts);\n  const queryLoading = useSelector(state => state.loading.effects['debtAccounts/query']);\n  const [ dataAndPagination ] = usePaginationAndData(queryResponse);\n\n  return (\n    <Spin spinning={queryLoading} size=\"large\">\n      { dataAndPagination.data && dataAndPagination.data.length > 0 ?\n        (\n          <Space {...spaceVProp}>\n            { dataAndPagination.data.map(item => <ItemCard key={item.id} account={item} />) }\n            <Pagination {...dataAndPagination.pagination} onChange={paginationChange} />\n          </Space>\n        ) :\n        (<Empty style={{ marginTop: 50 }} />)\n      }\n    </Spin>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/debt-accounts/index.jsx",
    "content": "import { Space } from 'antd';\nimport { spaceVProp }  from '@/utils/var';\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport GeneralBar from './GeneralBar';\nimport List from './List';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.account.list'), t('menu.debt.account')]} />\n      <GeneralBar />\n      <List />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/debt-accounts/index.less",
    "content": ".item-card-collapse {\n  :global {\n    .ant-collapse-content-box {\n      padding: 0 !important;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/debt-accounts/model.js",
    "content": "import {history} from \"umi\";\nimport modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport {query, sum} from \"@/services/debt-account\";\n\nexport default modelExtend(model, {\n  namespace: 'debtAccounts',\n  state: {\n    queryResponse: undefined,\n    sumResponse: undefined,\n    currentTime: Date.now()\n  },\n  effects: {\n    *sum(_, { call, put }) {\n      const response = yield call(sum);\n      yield put({\n        type: 'updateState',\n        payload: { sumResponse: response },\n      });\n    },\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, { ...payload, ...{ enable: true } });\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n    *refresh(_, { call, put, select }) {\n      yield put({ type: 'query', payload: history.location.query });\n      yield put({ type: 'sum' });\n      // 更新账户的流水记录\n      yield put({ type: 'updateState', payload: {currentTime: new Date()} });\n    }\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/debt-accounts') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/document.ejs",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\"/>\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/>\n  <title>九快记账</title>\n</head>\n<body>\n<div id=\"root\">\n  <style>\n    html,\n    body,\n    #root {\n      height: 100%;\n      margin: 0;\n      padding: 0;\n    }\n\n    .page-loading-warp {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 98px;\n    }\n\n    .ant-spin {\n      position: absolute;\n      display: none;\n      -webkit-box-sizing: border-box;\n      box-sizing: border-box;\n      margin: 0;\n      padding: 0;\n      color: rgba(0, 0, 0, 0.65);\n      color: #1890ff;\n      font-size: 14px;\n      font-variant: tabular-nums;\n      line-height: 1.5;\n      text-align: center;\n      list-style: none;\n      opacity: 0;\n      -webkit-transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);\n      transition: -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);\n      transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);\n      transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),\n      -webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);\n      -webkit-font-feature-settings: \"tnum\";\n      font-feature-settings: \"tnum\";\n    }\n\n    .ant-spin-spinning {\n      position: static;\n      display: inline-block;\n      opacity: 1;\n    }\n\n    .ant-spin-dot {\n      position: relative;\n      display: inline-block;\n      width: 20px;\n      height: 20px;\n      font-size: 20px;\n    }\n\n    .ant-spin-dot-item {\n      position: absolute;\n      display: block;\n      width: 9px;\n      height: 9px;\n      background-color: #1890ff;\n      border-radius: 100%;\n      -webkit-transform: scale(0.75);\n      -ms-transform: scale(0.75);\n      transform: scale(0.75);\n      -webkit-transform-origin: 50% 50%;\n      -ms-transform-origin: 50% 50%;\n      transform-origin: 50% 50%;\n      opacity: 0.3;\n      -webkit-animation: antspinmove 1s infinite linear alternate;\n      animation: antSpinMove 1s infinite linear alternate;\n    }\n\n    .ant-spin-dot-item:nth-child(1) {\n      top: 0;\n      left: 0;\n    }\n\n    .ant-spin-dot-item:nth-child(2) {\n      top: 0;\n      right: 0;\n      -webkit-animation-delay: 0.4s;\n      animation-delay: 0.4s;\n    }\n\n    .ant-spin-dot-item:nth-child(3) {\n      right: 0;\n      bottom: 0;\n      -webkit-animation-delay: 0.8s;\n      animation-delay: 0.8s;\n    }\n\n    .ant-spin-dot-item:nth-child(4) {\n      bottom: 0;\n      left: 0;\n      -webkit-animation-delay: 1.2s;\n      animation-delay: 1.2s;\n    }\n\n    .ant-spin-dot-spin {\n      -webkit-transform: rotate(45deg);\n      -ms-transform: rotate(45deg);\n      transform: rotate(45deg);\n      -webkit-animation: antrotate 1.2s infinite linear;\n      animation: antRotate 1.2s infinite linear;\n    }\n\n    .ant-spin-lg .ant-spin-dot {\n      width: 32px;\n      height: 32px;\n      font-size: 32px;\n    }\n\n    .ant-spin-lg .ant-spin-dot i {\n      width: 14px;\n      height: 14px;\n    }\n\n    @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {\n      .ant-spin-blur {\n        background: #fff;\n        opacity: 0.5;\n      }\n    }\n\n    @-webkit-keyframes antSpinMove {\n      to {\n        opacity: 1;\n      }\n    }\n\n    @keyframes antSpinMove {\n      to {\n        opacity: 1;\n      }\n    }\n\n    @-webkit-keyframes antRotate {\n      to {\n        -webkit-transform: rotate(405deg);\n        transform: rotate(405deg);\n      }\n    }\n\n    @keyframes antRotate {\n      to {\n        -webkit-transform: rotate(405deg);\n        transform: rotate(405deg);\n      }\n    }\n  </style>\n  <div style=\"\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    justify-content: center;\n    height: 100%;\n    min-height: 420px;\n  \">\n    <div class=\"page-loading-warp\">\n      <div class=\"ant-spin ant-spin-lg ant-spin-spinning\">\n        <span class=\"ant-spin-dot ant-spin-dot-spin\">\n          <i class=\"ant-spin-dot-item\"></i>\n          <i class=\"ant-spin-dot-item\"></i>\n          <i class=\"ant-spin-dot-item\"></i>\n          <i class=\"ant-spin-dot-item\"></i>\n        </span>\n      </div>\n    </div>\n\n  </div>\n</div>\n\n</body>\n</html>\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/expenses/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport { Form, Row, Col, Button, Space} from 'antd';\nimport {\n  useResponseSelectData,\n  useCategoryTreeSelectData,\n} from \"@/utils/hooks\";\nimport { filterFormProp } from \"@/utils/var\";\nimport { searchHandlerWithCategory } from \"@/utils/util\";\nimport {showFlowModal} from '@/utils/flow';\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAmountRange from \"@/components/FormItemAmountRange\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport FormItemCategories from \"@/components/FormItemCategories\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport FormItemStatus from \"@/components/FormItemStatus\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['expenses/query']);\n\n  useEffect(() => {\n    if (!tagResponse) dispatch({ type: 'tag/fetchExpenseable' });\n    if (!payeeResponse) dispatch({ type: 'payee/fetchExpenseable' });\n    if (!accountResponse) dispatch({ type: 'account/fetchExpenseable' });\n    if (!querySimpleResponse) dispatch({ type: 'expenseCategory/fetchSimple' });\n  }, []);\n\n  const { getExpenseableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getExpenseableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees] = useResponseSelectData(payeeResponse);\n\n  const { getExpenseableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseSelectData(accountResponse);\n\n  const { querySimpleResponse } = useSelector(state => state.expenseCategory);\n  const [categories] = useCategoryTreeSelectData(querySimpleResponse);\n\n  function addHandler() {\n    showFlowModal(1, 1, {});\n  }\n\n  const [dateRadioValue, setDateRadioValue] = useState();\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={20}>\n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemAmountRange />\n        <FormItemDescriptionSearch />\n        <FormItemCategories data={categories} />\n        <FormItemPayees data={payees} />\n        <FormItemAccounts data={accounts} />\n        <FormItemStatus />\n        <FormItemTags data={tags} />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" onClick={addHandler}>{t('new')}</Button>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandlerWithCategory(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/expenses/RecordTable.jsx",
    "content": "import {history, useDispatch, useSelector} from 'umi';\nimport {Table, Space, message, Modal, Descriptions, Dropdown, Menu, Tooltip} from 'antd';\nimport { DownOutlined } from '@ant-design/icons';\nimport { remove, confirm } from '@/services/flow';\nimport {useResultPaginationAndData, useImageEnable, useDescriptionEnable, useTimeFormat} from '@/utils/hooks';\nimport { handleTableChange } from \"@/utils/util\";\nimport {showFlowModal, imageHandler} from '@/utils/flow';\nimport { tableProp }  from '@/utils/var';\nimport {createTimeCol, amountCol, statusCol} from '@/utils/columns';\nimport FlowTagDisplay from '@/components/FlowTagDisplay';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const { queryResponse } = useSelector(state => state.expenses);\n  const queryLoading = useSelector(state => state.loading.effects['expenses/query']);\n  const [ dataAndPagination ] = useResultPaginationAndData(queryResponse);\n  const imageEnable = useImageEnable();\n  const descriptionEnable = useDescriptionEnable();\n  const timeFormat = useTimeFormat();\n\n  let columns = [];\n  if (descriptionEnable) {\n    columns.push({\n      title: t('description'),\n      dataIndex: 'description',\n    });\n  }\n  columns = columns.concat(\n    [\n      amountCol(),\n      createTimeCol(timeFormat),\n      {\n        title: t('category'),\n        dataIndex: 'categoryName',\n      },\n      {\n        title: t('account'),\n        dataIndex: 'accountName',\n      },\n      {\n        title: t('flow.tag'),\n        dataIndex: 'tags',\n        render: (tags, record) => <>{tags.map(i => <FlowTagDisplay record={record} data={i} key={i.tagId} />)}</>\n      },\n      {\n        title: t('flow.payee'),\n        dataIndex: 'payee',\n        sorter: true,\n        render: payee => <>{payee ? payee.name : null}</>\n      },\n      statusCol(),\n      {\n        title: t('operation'),\n        key: 'operation',\n        width: 100,\n        align: 'center',\n        render: (_, record) => {\n          return (\n            <Space size=\"small\">\n              <a onClick={() => copyHandler(record)}>{t('copy')}</a>\n              <Dropdown overlay={\n                <Menu>\n                  {imageEnable ? <Menu.Item onClick={() => imageHandler(record)}>{t('image')}</Menu.Item> : null}\n                  <Menu.Item onClick={() => updateHandler(record)}>{t('update')}</Menu.Item>\n                  <Menu.Item disabled={record.status !== 2} onClick={() => confirmHandler(record)}>{t('confirm')}</Menu.Item>\n                  <Menu.Item disabled={record.status == 2 || record.amount < 0} onClick={() => refundHandler(record)}>{t('refund')}</Menu.Item>\n                  <Menu.Item disabled={record.status == 3} onClick={() => deleteHandler(record)}>\n                    {\n                      record.status == 3 ?\n                      <Tooltip title={t('delete.tooltip')}>{t('delete')}</Tooltip>:\n                      t('delete')\n                    }\n                  </Menu.Item>\n                </Menu>\n              }>\n                <a>\n                  {t('more')} <DownOutlined />\n                </a>\n              </Dropdown>\n            </Space>\n          )\n        }\n      }\n    ]\n  );\n\n  function copyHandler(record) {\n    showFlowModal(1, 3, {...record});\n  }\n\n  const updateHandler = (record) => {\n    showFlowModal(1, 2, {...record});\n  }\n\n  const messageDeleteConfirm = t('delete.confirm', { name: '' });\n  const messageDeleteConfirmBalance = t('delete.confirm.balance');\n  const messageOperationSuccess = t('operation.success');\n  function deleteHandler(record) {\n    Modal.confirm({\n      title: record.status === 2 ? messageDeleteConfirm : messageDeleteConfirmBalance,\n      onOk: async () => {\n        const response = await remove(record);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'expenses/query', payload: history.location.query });\n        }\n      }\n    });\n  }\n\n  function refundHandler(record) {\n    showFlowModal(1, 4, {...record});\n  }\n\n  const messageConfirmOperation = t('confirm.operation');\n  function confirmHandler(record) {\n    Modal.confirm({\n      title: messageConfirmOperation,\n      onOk: async () => {\n        const response = await confirm(record);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'expenses/query', payload: history.location.query });\n        }\n      }\n    });\n  }\n\n  return (\n    <Table\n      {...tableProp}\n      columns={columns}\n      expandable={{\n        expandedRowRender: record => <Descriptions size=\"small\" bordered>\n          {record.needConvert ? <Descriptions.Item label={t('convertCurrency')+record.toCurrencyCode}>{record.convertedAmount}</Descriptions.Item> : null}\n          {record.notes ? <Descriptions.Item label={t('notes')}>{record.notes}</Descriptions.Item> : null}\n        </Descriptions>,\n        rowExpandable: record => record.notes || record.needConvert,\n      }}\n      dataSource={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      loading={queryLoading}\n      onChange={handleTableChange}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/expenses/TotalAlert.jsx",
    "content": "import {useMemo} from \"react\";\nimport { useSelector } from 'umi';\nimport AlertTotalSearch from \"@/components/AlertTotalSearch\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const { queryResponse } = useSelector(state => state.expenses);\n  const [ responseData ] = useResponseData(queryResponse);\n  const total = useMemo(() => {\n    if (responseData.result) {\n      return responseData.total;\n    } else {\n      return 0;\n    }\n  }, [responseData]);\n\n  return (\n    <AlertTotalSearch message={t('gross.expense') + ': ' + total} />\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/expenses/index.jsx",
    "content": "import { Space } from 'antd';\nimport Breadcrumb from '@/components/Breadcrumb';\nimport {spaceVProp} from \"@/utils/var\";\nimport OperationBar from './OperationBar';\nimport TotalAlert from './TotalAlert';\nimport RecordTable from './RecordTable';\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.bookkeeping'), t('menu.expense')]} />\n      <OperationBar />\n      <TotalAlert />\n      <RecordTable />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/expenses/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport { model } from '@/utils/model';\nimport { query } from '@/services/expense';\n\nexport default modelExtend(model, {\n  namespace: 'expenses',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/expenses') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/flows/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport { Form, Row, Col, Button, Select, Space } from 'antd';\nimport { filterFormProp } from \"@/utils/var\";\nimport {searchHandler} from \"@/utils/util\";\nimport {useResponseSelectData, useCategoryTreeSelectData} from \"@/utils/hooks\";\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAmountRange from \"@/components/FormItemAmountRange\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport FormItemStatus from \"@/components/FormItemStatus\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['flows/query']);\n\n  useEffect(() => {\n    if (!accountResponse) dispatch({ type: 'account/fetchEnable' });\n    if (!payeeResponse) dispatch({ type: 'payee/fetchEnable' });\n    if (!tagResponse) dispatch({ type: 'tag/fetchEnable' });\n  }, []);\n\n  const { getEnableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseSelectData(accountResponse);\n\n  const { getEnableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees] = useResponseSelectData(payeeResponse);\n\n  const { getEnableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const [dateRadioValue, setDateRadioValue] = useState();\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={20}>        \n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemAmountRange />\n        <FormItemDescriptionSearch />\n        <Col flex=\"200px\">\n          <Form.Item label={t('flow.type')} name=\"type\">\n            <Select allowClear={true}>\n              <Select.Option value={1}>{t('expense')}</Select.Option>\n              <Select.Option value={2}>{t('income')}</Select.Option>\n              <Select.Option value={3}>{t('transfer')}</Select.Option>\n              <Select.Option value={4}>{t('adjust.balance')}</Select.Option>\n            </Select>\n          </Form.Item>\n        </Col>\n        <FormItemAccounts data={accounts} />\n        <FormItemPayees data={payees} />\n        <FormItemStatus />\n        <FormItemTags data={tags} />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/flows/RecordTable.jsx",
    "content": "import { useSelector } from 'umi';\nimport {useResultPaginationAndData} from '@/utils/hooks';\nimport {handleTableChange} from \"@/utils/util\";\nimport FlowRecordTable from '@/components/FlowRecordTable';\n\nexport default () => {\n\n  const queryLoading = useSelector(state => state.loading.effects['flows/query']);\n  const { queryResponse } = useSelector(state => state.flows);\n  const [ dataAndPagination ] = useResultPaginationAndData(queryResponse);\n\n  return (\n    <FlowRecordTable\n      bordered={true}\n      noBook={true}\n      data={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      queryLoading={queryLoading}\n      tableChangeHandler={handleTableChange}\n    />\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/flows/TotalAlert.jsx",
    "content": "import {useMemo} from \"react\";\nimport { useSelector } from 'umi';\nimport { Space } from 'antd';\nimport {useResponseData} from \"@/utils/hooks\";\nimport AlertTotalSearch from \"@/components/AlertTotalSearch\";\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const { queryResponse } = useSelector(state => state.flows);\n  const [ responseData ] = useResponseData(queryResponse);\n  const total = useMemo(() => {\n    if (responseData.result) {\n      return [responseData.expense, responseData.income, responseData.surplus]\n    } else {\n      return [0, 0, 0]\n    }\n  }, [responseData]);\n\n  return (\n    <Space>\n      <AlertTotalSearch message={t('gross.expense') + ': ' + total[0]} />\n      <AlertTotalSearch message={t('gross.income') + ': ' + total[1]} />\n      <AlertTotalSearch message={t('gross.surplus') + ': ' + total[2]} />\n    </Space>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/flows/index.jsx",
    "content": "import { Space } from 'antd';\nimport {spaceVProp} from '@/utils/var';\nimport Breadcrumb from '@/components/Breadcrumb';\nimport OperationBar from './OperationBar';\nimport TotalAlert from './TotalAlert';\nimport RecordTable from './RecordTable';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.bookkeeping'), t('menu.flow')]} />\n      <OperationBar />\n      <TotalAlert />\n      <RecordTable />\n    </Space>\n  );\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/flows/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport { model } from '@/utils/model';\nimport { query } from '@/services/flow';\n\nexport default modelExtend(model, {\n  namespace: 'flows',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/flows') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/groups/OperationBar.jsx",
    "content": "import { useDispatch } from 'umi';\nimport { Button } from 'antd';\nimport OperationModal from './OperationModal';\nimport t from \"@/utils/translate\";\nimport request from \"@/utils/request\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const addHandler = () => {\n    dispatch({ type: 'modal/show', payload: {component: OperationModal, type: 1, currentItem: null }});\n  }\n\n  return (\n    <Button type=\"primary\" onClick={ addHandler }>{t('new')}</Button>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/groups/OperationModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport { Form, Input, Select} from 'antd';\nimport { create, update } from '@/services/group';\nimport {getNull} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\nimport FormModal from \"@/components/FormModal\";\nimport {nameRules, notesRules, requiredRules} from \"@/utils/rules\";\nimport {useCurrencyResponseSelectData} from \"@/utils/hooks\";\nimport styles from './index.less';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const [initialValues, setInitialValues] = useState(currentItem)\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        ...{\n          defaultCurrencyCode: 'CNY'\n        }\n      });\n    } else {\n      setInitialValues({...getNull(form.getFieldsValue()), ...currentItem});\n    }\n  }, [visible]);\n\n  useEffect(() => {\n    if (!currencyResponse) dispatch({ type: 'currency/fetchAll' });\n  }, []);\n  const { getAllResponse : currencyResponse } = useSelector(state => state.currency);\n  const [currencyList] = useCurrencyResponseSelectData(currencyResponse);\n\n  function successHandler() {\n    dispatch({ type: 'groups/query' });\n    dispatch({ type: 'session/fetchSession' })\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + t('group')}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={successHandler}\n    >\n      <Form form={form} className={styles['form2']}>\n        <Form.Item label={t('group.name')} name=\"name\" rules={nameRules()}>\n          <Input />\n        </Form.Item>\n        <Form.Item label={t('default.currency')} name=\"defaultCurrencyCode\" rules={requiredRules()} labelCol={{ style: { width: 68 } }}>\n          <Select options={currencyList} showArrow showSearch filterOption optionFilterProp={\"label\"} disabled={type === 2} />\n        </Form.Item>\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/groups/RecordTable.jsx",
    "content": "import { useEffect } from 'react';\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport { Button, message, Table, Space, Modal } from \"antd\";\nimport { remove } from '@/services/group';\nimport { setDefaultGroup } from \"@/services/user\";\nimport {tableProp} from \"@/utils/var\";\nimport {handleTableChange} from \"@/utils/util\";\nimport {usePaginationAndData} from \"@/utils/hooks\";\nimport OperationModal from \"./OperationModal\";\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { defaultGroup } = useSelector(state => state.session);\n  useEffect(() => {\n    if (!defaultGroup) dispatch({ type: 'session/fetchSession' });\n  }, [defaultGroup]);\n\n  const { queryResponse } = useSelector(state => state.groups);\n  const queryLoading = useSelector(state => state.loading.effects['groups/query']);\n  const [dataAndPagination] = usePaginationAndData(queryResponse);\n\n  const updateHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: OperationModal, type: 2, currentItem: record } });\n  }\n\n  const intl = useIntl();\n  const messageOperationSuccess = t('operation.success');\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.name }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'groups/query' });\n        }\n      }\n    });\n  }\n\n  const setDefaultHandler = async (record) => {\n    const response = await setDefaultGroup(record.id);\n    if (response && response.success) {\n      message.success(messageOperationSuccess);\n      window.location.reload();\n    }\n  }\n\n  const columns = [\n    {\n      title: t('group.name'),\n      dataIndex: 'name',\n    },\n    {\n      title: t('default.currency'),\n      dataIndex: 'defaultCurrencyCode',\n      width: 70,\n    },\n    {\n      title: t('notes'),\n      dataIndex: 'notes',\n    },\n    {\n      title: t('operation'),\n      key: 'operation',\n      width: 190,\n      align: 'center',\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" onClick={() => updateHandler(record)}>{t('update')}</Button>\n            <Button size=\"small\" disabled={defaultGroup && record.id === defaultGroup.id} onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n            <Button size=\"small\" disabled={defaultGroup && record.id === defaultGroup.id} onClick={() => setDefaultHandler(record)}>{t('set.default')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      rowClassName={record => {\n        if (defaultGroup && record.id === defaultGroup.id) return 'table-color-default';\n      }}\n      size=\"middle\"\n      columns={columns}\n      dataSource={dataAndPagination.data}\n      loading={queryLoading}\n      pagination={dataAndPagination.pagination}\n      onChange={handleTableChange}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/groups/index.jsx",
    "content": "import {Space} from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport RecordTable from './RecordTable';\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp} size=\"large\">\n      <Breadcrumb data={[t('menu.settings'), t('menu.group')]} />\n      <OperationBar />\n      <RecordTable />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/groups/index.less",
    "content": ".form2 {\n  :global {\n    .ant-form-item-label {\n      width: 45px;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/groups/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport { model } from '@/utils/model';\nimport { query } from '@/services/group';\n\nexport default modelExtend(model, {\n  namespace: 'groups',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/groups') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/incomes/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Row, Col, Button, Space} from 'antd';\nimport { useResponseSelectData, useCategoryTreeSelectData } from \"@/utils/hooks\";\nimport { filterFormProp } from \"@/utils/var\";\nimport { searchHandlerWithCategory } from \"@/utils/util\";\nimport {showFlowModal} from '@/utils/flow';\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAmountRange from \"@/components/FormItemAmountRange\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport FormItemCategories from \"@/components/FormItemCategories\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport FormItemStatus from \"@/components/FormItemStatus\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['incomes/query']);\n\n  useEffect(() => {\n    if (!tagResponse) dispatch({ type: 'tag/fetchIncomeable' });\n    if (!payeeResponse) dispatch({ type: 'payee/fetchIncomeable' });\n    if (!accountResponse) dispatch({ type: 'account/fetchIncomeable' });\n    if (!querySimpleResponse) dispatch({ type: 'incomeCategory/fetchSimple' });\n  }, []);\n\n  const { getIncomeableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getIncomeableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees] = useResponseSelectData(payeeResponse);\n\n  const { getIncomeableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseSelectData(accountResponse);\n\n  const { querySimpleResponse } = useSelector(state => state.incomeCategory);\n  const [categories] = useCategoryTreeSelectData(querySimpleResponse);\n\n  function addHandler() {\n    showFlowModal(2, 1, {});\n  }\n\n  const [dateRadioValue, setDateRadioValue] = useState();\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={20}>        \n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemAmountRange />\n        <FormItemDescriptionSearch />\n        <FormItemCategories data={categories} />\n        <FormItemPayees data={payees} />\n        <FormItemAccounts data={accounts} />\n        <FormItemStatus />\n        <FormItemTags data={tags} />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" onClick={addHandler}>{t('new')}</Button>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandlerWithCategory(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/incomes/RecordTable.jsx",
    "content": "import { history, useDispatch, useSelector } from 'umi';\nimport {Table, Space, message, Modal, Descriptions, Dropdown, Menu, Tooltip} from 'antd';\nimport {DownOutlined} from \"@ant-design/icons\";\nimport { remove, confirm } from '@/services/flow';\nimport {useDescriptionEnable, useImageEnable, useResultPaginationAndData, useTimeFormat} from '@/utils/hooks';\nimport {handleTableChange } from \"@/utils/util\";\nimport {showFlowModal, imageHandler} from '@/utils/flow';\nimport { tableProp }  from '@/utils/var';\nimport {createTimeCol, amountCol, statusCol} from '@/utils/columns';\nimport FlowTagDisplay from \"@/components/FlowTagDisplay\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const { queryResponse } = useSelector(state => state.incomes);\n  const queryLoading = useSelector(state => state.loading.effects['incomes/query']);\n  const [ dataAndPagination ] = useResultPaginationAndData(queryResponse);\n  const imageEnable = useImageEnable();\n  const descriptionEnable = useDescriptionEnable();\n  const timeFormat = useTimeFormat();\n\n  let columns = [];\n  if (descriptionEnable) {\n    columns.push({\n      title: t('description'),\n      dataIndex: 'description',\n    });\n  }\n  columns = columns.concat(\n    amountCol(),\n    createTimeCol(timeFormat),\n    {\n      title: t('category'),\n      dataIndex: 'categoryName',\n    },\n    {\n      title: t('account'),\n      dataIndex: 'accountName',\n    },\n    {\n      title: t('flow.tag'),\n      dataIndex: 'tags',\n      render: (tags, record) => <>{tags.map(i => <FlowTagDisplay record={record} data={i} key={i.tagId} />)}</>\n    },\n    {\n      title: t('flow.payee'),\n      dataIndex: 'payee',\n      sorter: true,\n      render: payee => <>{payee ? payee.name : null}</>\n    },\n    statusCol(),\n    {\n      title: t('operation'),\n      key: 'operation',\n      width: 100,\n      align: 'center',\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <a onClick={() => copyHandler(record)}>{t('copy')}</a>\n            <Dropdown overlay={\n              <Menu>\n                {imageEnable ? <Menu.Item onClick={() => imageHandler(record)}>{t('image')}</Menu.Item> : null}\n                <Menu.Item onClick={() => updateHandler(record)}>{t('update')}</Menu.Item>\n                <Menu.Item disabled={record.status !== 2} onClick={() => confirmHandler(record)}>{t('confirm')}</Menu.Item>\n                <Menu.Item disabled={record.status == 2 || record.amount < 0} onClick={() => refundHandler(record)}>{t('refund')}</Menu.Item>\n                <Menu.Item disabled={record.status == 3} onClick={() => deleteHandler(record)}>\n                  {\n                    record.status == 3 ?\n                    <Tooltip title={t('delete.tooltip')}>{t('delete')}</Tooltip>:\n                    t('delete')\n                  }\n                </Menu.Item>\n              </Menu>\n            }>\n              <a>\n                {t('more')} <DownOutlined />\n              </a>\n            </Dropdown>\n          </Space>\n        )\n      }\n    }\n  );\n\n  function copyHandler(record) {\n    showFlowModal(2, 3, {...record});\n  }\n\n  const updateHandler = (record) => {\n    showFlowModal(2, 2, {...record});\n  }\n\n  const messageDeleteConfirm = t('delete.confirm', { name: '' });\n  const messageDeleteConfirmBalance = t('delete.confirm.balance');\n  const messageOperationSuccess = t('operation.success');\n  function deleteHandler(record) {\n    Modal.confirm({\n      title: record.status === 2 ? messageDeleteConfirm : messageDeleteConfirmBalance,\n      onOk: async () => {\n        const response = await remove(record);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'incomes/query', payload: history.location.query });\n        }\n      }\n    });\n  }\n\n  function refundHandler(record) {\n    showFlowModal(2, 4, {...record});\n  }\n\n  const messageConfirmOperation = t('confirm.operation');\n  function confirmHandler(record) {\n    Modal.confirm({\n      title: messageConfirmOperation,\n      onOk: async () => {\n        const response = await confirm(record);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'incomes/query', payload: history.location.query });\n        }\n      }\n    });\n  }\n\n  return (\n    <Table\n      {...tableProp}\n      columns={columns}\n      expandable={{\n        expandedRowRender: record => <Descriptions size=\"small\" bordered>\n          {record.needConvert ? <Descriptions.Item label={t('convertCurrency')+record.toCurrencyCode}>{record.convertedAmount}</Descriptions.Item> : null}\n          {record.notes ? <Descriptions.Item label={t('notes')}>{record.notes}</Descriptions.Item> : null}\n        </Descriptions>,\n        rowExpandable: record => record.notes || record.needConvert,\n      }}\n      dataSource={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      loading={queryLoading}\n      onChange={handleTableChange}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/incomes/TotalAlert.jsx",
    "content": "import {useMemo} from \"react\";\nimport { useSelector } from 'umi';\nimport AlertTotalSearch from \"@/components/AlertTotalSearch\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const { queryResponse } = useSelector(state => state.incomes);\n  const [ responseData ] = useResponseData(queryResponse);\n  const total = useMemo(() => {\n    if (responseData.result) {\n      return responseData.total;\n    } else {\n      return 0;\n    }\n  }, [responseData]);\n\n  return (\n    <AlertTotalSearch message={t('gross.income') + ': ' + total} />\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/incomes/index.jsx",
    "content": "import { Space } from 'antd';\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from '@/components/Breadcrumb';\nimport OperationBar from './OperationBar';\nimport RecordTable from './RecordTable';\nimport TotalAlert from \"./TotalAlert\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.bookkeeping'), t('menu.income')]} />\n      <OperationBar />\n      <TotalAlert />\n      <RecordTable />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/incomes/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { query } from '@/services/income';\n\nexport default modelExtend(model, {\n  namespace: 'incomes',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/incomes') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/index.jsx",
    "content": "import { Redirect } from 'umi'\n\nexport default function() {\n  return <Redirect to=\"dashboard\" />\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/items/OperationBar.jsx",
    "content": "import { useDispatch } from 'umi';\nimport { Button } from 'antd';\nimport OperationModal from './OperationModal';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const addHandler = () => {\n    dispatch({ type: 'modal/show', payload: {component: OperationModal, type: 1, currentItem: null }});\n  }\n\n  return (\n    <Button type=\"primary\" onClick={ addHandler }>{t('new')}</Button>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/items/OperationModal.jsx",
    "content": "import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Modal, Form, Input, message, DatePicker, Select} from 'antd';\nimport { create, update } from '@/services/item';\nimport {getNull, getTimeEnable, getTimeFormat, validateForm} from \"@/utils/util\";\nimport t from \"@/utils/translate\";\nimport FormModal from \"@/components/FormModal\";\nimport {nameRules, notesRules, timeRequiredRules, timeRangeRequiredRules, requiredRules} from \"@/utils/rules\";\nimport styles from './index.less';\nimport moment from \"moment\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n\n  const { visible, type, currentItem } = useSelector(state => state.modal);\n\n  const [initialValues, setInitialValues] = useState(currentItem);\n  useEffect(() => {\n    if (!visible) return;\n    if (type === 1) {\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        'type': 1,\n        'repeatType': 2,\n        'interval': 1\n      });\n      setRepeatFlag(1);\n    } else {\n      let currentItemCopy = JSON.parse(JSON.stringify(currentItem));\n      currentItemCopy.startDate = moment(currentItemCopy.startDate);\n      currentItemCopy.endDate = moment(currentItemCopy.endDate);\n      currentItemCopy.dateRange = [currentItemCopy.startDate, currentItemCopy.endDate];\n      const repeatFlag = currentItemCopy.repeatType == 0 ? 1 : 2;\n      setInitialValues({\n        ...getNull(form.getFieldsValue()),\n        ...currentItemCopy,\n        'type': repeatFlag,\n      });\n      setRepeatFlag(repeatFlag);\n    }\n  }, [visible, type, currentItem]);\n\n  const [repeatFlag, setRepeatFlag] = useState(1);\n\n  function successHandler() {\n    dispatch({ type: 'items/query' });\n  }\n\n  function parseValues(values) {\n    if (values.startDate) values.startDate = values.startDate.valueOf();\n    if (values.dateRange && values.dateRange[0]) {\n      values.startDate = values.dateRange[0].valueOf();\n    }\n    if (values.dateRange && values.dateRange[1]) {\n      values.endDate = values.dateRange[1].valueOf();\n    }\n    delete values.dateRange;\n  }\n\n  return (\n    <FormModal\n      title={(type === 1 ? t('new') : t('update')) + '提醒事项'}\n      form={form}\n      initialValues={initialValues}\n      create={create}\n      update={update}\n      onSuccess={successHandler}\n      onParseValues={parseValues}\n    >\n      <Form form={form} className={styles['form2']}>\n        <Form.Item label=\"类型\" name=\"type\" rules={requiredRules()}>\n          <Select disabled={type === 2} value={repeatFlag} onChange={(value) => setRepeatFlag(value)}>\n           <Select.Option value={1}>单次</Select.Option>\n           <Select.Option value={2}>多次</Select.Option>\n          </Select>\n        </Form.Item>\n        <Form.Item label='标题' name=\"title\" rules={nameRules()}>\n          <Input />\n        </Form.Item>\n        {repeatFlag == 1\n            ? <Form.Item label='执行日期' name=\"startDate\" rules={timeRequiredRules()}>\n                <DatePicker disabled={type === 2} showTime={false} format='YYYY-MM-DD' allowClear={false}/>\n              </Form.Item>\n            : <div>\n                <Form.Item label='起止日期' name=\"dateRange\" rules={timeRangeRequiredRules()}>\n                  <DatePicker.RangePicker disabled={[type === 2, false]} showTime={false} format='YYYY-MM-DD' />\n                </Form.Item>\n                <Form.Item label='间隔周期类型' name=\"repeatType\" rules={requiredRules()}>\n                  <Select disabled={type === 2}>\n                    <Select.Option value={1}>天</Select.Option>\n                    <Select.Option value={2}>月</Select.Option>\n                    <Select.Option value={3}>年</Select.Option>\n                  </Select>\n                </Form.Item>\n                <Form.Item label='间隔周期数' name=\"interval\" rules={requiredRules()}>\n                  <Input disabled={type === 2} />\n                </Form.Item>\n              </div>\n        }\n        <Form.Item label={t('notes')} name=\"notes\" rules={notesRules()}>\n          <Input.TextArea rows={4} />\n        </Form.Item>\n      </Form>\n    </FormModal>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/items/RecordTable.jsx",
    "content": "import { useEffect } from 'react';\nimport {useDispatch, useIntl, useSelector} from 'umi';\nimport { Button, message, Table, Space, Modal } from \"antd\";\nimport { remove, run, recall } from '@/services/item';\nimport {tableProp} from \"@/utils/var\";\nimport {handleTableChange} from \"@/utils/util\";\nimport {usePaginationAndData} from \"@/utils/hooks\";\nimport OperationModal from \"./OperationModal\";\nimport t from \"@/utils/translate\";\nimport moment from \"moment\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryResponse } = useSelector(state => state.items);\n  const queryLoading = useSelector(state => state.loading.effects['items/query']);\n  const [dataAndPagination] = usePaginationAndData(queryResponse);\n\n  const updateHandler = (record) => {\n    dispatch({ type: 'modal/show', payload: {component: OperationModal, type: 2, currentItem: record } });\n  }\n\n  const messageOperationSuccess = t('operation.success');\n  const runHandler = async (record) => {\n    const response = await run(record.id);\n    if (response) {\n      if (response.success) {\n        message.success(messageOperationSuccess);\n        dispatch({ type: 'items/query' });\n      }\n    }\n  }\n\n  const recallHandler = async (record) => {\n    const response = await recall(record.id);\n    if (response) {\n      if (response.success) {\n        message.success(messageOperationSuccess);\n        dispatch({ type: 'items/query' });\n      }\n    }\n  }\n\n  const intl = useIntl();\n  const deleteHandler = async (record) => {\n    Modal.confirm({\n      title: intl.formatMessage({id:'delete.confirm'}, { name: record.title }),\n      onOk: async () => {\n        const response = await remove(record.id);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'items/query' });\n        }\n      }\n    });\n  }\n\n  const columns = [\n    {\n      title: '标题',\n      dataIndex: 'title',\n    },\n    {\n      title: '开始日期',\n      dataIndex: 'startDate',\n      sorter: true,\n      width: 85,\n      render: time => moment(time).format('YYYY-MM-DD')\n    },\n    {\n      title: '结束日期',\n      dataIndex: 'endDate',\n      sorter: true,\n      width: 85,\n      render: time => time ? moment(time).format('YYYY-MM-DD') : ''\n    },\n    {\n      title: '执行频率',\n      dataIndex: 'repeatDescription',\n      width: 95,\n    },\n    {\n      title: '下次执行',\n      dataIndex: 'nextDate',\n      sorter: true,\n      width: 85,\n      render: time => time ? moment(time).format('YYYY-MM-DD') : ''\n    },\n    {\n      title: '倒计时（天）',\n      dataIndex: 'countDown',\n      width: 85,\n    },\n    {\n      title: '总次数',\n      dataIndex: 'totalCount',\n      sorter: true,\n      width: 65,\n    },\n    {\n      title: '已执行次数',\n      dataIndex: 'runCount',\n      sorter: true,\n      width: 85,\n    },\n    {\n      title: '剩余次数',\n      dataIndex: 'remainCount',\n      width: 65,\n    },\n    {\n      title: t('notes'),\n      dataIndex: 'notes',\n    },\n    {\n      title: t('operation'),\n      key: 'operation',\n      width: 220,\n      align: 'center',\n      render: (_, record) => {\n        return (\n          <Space size=\"small\">\n            <Button size=\"small\" disabled={record.totalCount <= record.runCount} onClick={() => runHandler(record)}>执行</Button>\n            <Button size=\"small\" disabled={record.runCount <= 0} onClick={() => recallHandler(record)}>撤回</Button>\n            <Button size=\"small\" onClick={() => updateHandler(record)}>{t('update')}</Button>\n            <Button size=\"small\" onClick={() => deleteHandler(record)}>{t('delete')}</Button>\n          </Space>\n        )\n      }\n    }\n  ];\n\n  return (\n    <Table\n      {...tableProp}\n      size=\"small\"\n      columns={columns}\n      dataSource={dataAndPagination.data}\n      loading={queryLoading}\n      pagination={dataAndPagination.pagination}\n      onChange={handleTableChange}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/items/index.jsx",
    "content": "import {Space} from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport RecordTable from './RecordTable';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  return (\n    <Space {...spaceVProp} size=\"large\">\n      <Breadcrumb data={['提醒事项']} />\n      <OperationBar />\n      <RecordTable />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/items/index.less",
    "content": ".form2 {\n  :global {\n    .ant-form-item-label {\n      width: 90px;\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/items/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport { model } from '@/utils/model';\nimport { query } from '@/services/item';\n\nexport default modelExtend(model, {\n  namespace: 'items',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/items') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/register/index.jsx",
    "content": "import { useEffect } from 'react';\nimport { Form, Input, Button, message } from 'antd';\nimport { Link, useDispatch, useSelector } from 'umi';\nimport { UserOutlined, LockOutlined, VerifiedOutlined, MailOutlined } from '@ant-design/icons';\nimport { userNameRules, passwordRules, emailRules, requiredRules }  from '@/utils/rules';\nimport t from '@/utils/translate';\n\nimport styles from './index.less';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const { registerResponse } = useSelector(state => state.userRegister);\n  const submitting = useSelector(state => state.loading.effects['userRegister/submit']);\n\n  const messageRegisterSuccess = t('register.success');\n  useEffect(() => {\n    if (registerResponse && registerResponse.success) {\n      message.success(messageRegisterSuccess);\n    }\n    return () => {\n      dispatch({ type: 'userRegister/clearRegisterResponse' });\n    }\n  }, [registerResponse]);\n\n  const handleSubmit = (values) => {\n    dispatch({ type: 'userRegister/submit', payload: { ...values }, });\n  };\n\n  return (\n    <div className={styles.main}>\n      <Form size=\"large\" onFinish={ handleSubmit }>\n        <Form.Item name=\"userName\" rules={ userNameRules() }>\n          <Input autoFocus prefix={<UserOutlined className=\"site-form-item-icon\" />} placeholder={t('placeholder.userName')} />\n        </Form.Item>\n        <Form.Item name=\"password\" rules={ passwordRules() }>\n          <Input prefix={<LockOutlined className=\"site-form-item-icon\" />} type=\"password\" placeholder={t('placeholder.password')} />\n        </Form.Item>\n        <Form.Item name=\"inviteCode\">\n          <Input prefix={<VerifiedOutlined className=\"site-form-item-icon\" />} placeholder={t('placeholder.invite.code')} />\n        </Form.Item>\n        <Form.Item name=\"email\" rules={ emailRules() }>\n          <Input prefix={<MailOutlined className=\"site-form-item-icon\" />} type=\"eamil\" placeholder={t('placeholder.email')} />\n        </Form.Item>\n        <div className={styles.form_row}>\n          <Button size=\"large\" type=\"primary\" htmlType=\"submit\" block loading={ submitting }>{t('register')}</Button>\n        </div>\n        <div className={styles.form_row} style={{textAlign: \"center\"}}>\n          <Link to=\"/signin\">{t('register.signin')}</Link>\n        </div>\n      </Form>\n    </div>\n  );\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/register/index.less",
    "content": "@import '~antd/es/style/themes/default.less';\n\n.main {\n  margin: 0 auto;\n  width: 368px;\n  @media screen and (max-width: @screen-sm) {\n    width: 95%;\n  }\n}\n.form_row {\n  margin-bottom: 15px;\n}"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/register/model.js",
    "content": "import { register } from '@/services/user';\n\nexport default {\n  namespace: 'userRegister',\n  state: {\n    registerResponse: undefined,\n  },\n  effects: {\n    *submit({ payload }, { call, put }) {\n      const response = yield call(register, payload);\n      yield put({\n        type: 'registerHandler',\n        payload: response,\n      });\n    }\n  },\n  reducers: {\n    registerHandler(state, { payload }) {\n      return { ...state, registerResponse: payload };\n    },\n    clearRegisterResponse(state) {\n      return { ...state, registerResponse: undefined };\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/asset-debt-trend/Chart.jsx",
    "content": "import {useSelector} from \"umi\";\nimport {Card} from \"antd\";\nimport Line from \"@/components/charts/Line\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const loading = useSelector(state => state.loading.effects['assetDebtTrendReports/query']);\n  const { queryResponse } = useSelector(state => state.assetDebtTrendReports);\n  const [data] = useResponseData(queryResponse);\n\n  const cols = {\n    month: {\n      range: [0, 1]\n    }\n  };\n\n  return (\n    <Card title={t('menu.asset.trend')} bordered={false} bodyStyle={{padding: '40px 10px'}}>\n      <Line loading={loading} data={data} scale={cols} height={400} padding={[ 20, 10, 100, 60 ]} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/asset-debt-trend/OperationBar.jsx",
    "content": "import {Form, Row, Col, Button, Space} from 'antd';\nimport { filterFormProp } from \"@/utils/var\";\nimport { searchHandler } from \"@/utils/util\";\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport t from '@/utils/translate';\nimport {useState} from \"react\";\nimport {useSelector} from \"umi\";\n\nexport default () => {\n\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['assetDebtTrendReports/query']);\n\n  const [dateRadioValue, setDateRadioValue] = useState(8);\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={8}>\n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n      </Row>\n      <Row>\n        <Col flex=\"auto\" style={{ textAlign:'right' }}>\n          <Space>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/asset-debt-trend/index.jsx",
    "content": "import { Space } from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport Chart from './Chart';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.report'), t('menu.asset.trend')]} />\n      <OperationBar />\n      <Chart />\n    </Space>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/asset-debt-trend/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getAssetDebtTrend } from \"@/services/report\";\n\nexport default modelExtend(model, {\n  namespace: 'assetDebtTrendReports',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(getAssetDebtTrend, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/reports/asset-debt-trend') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/balance-sheet/AssetCategory.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card } from \"antd\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport Pie from '@/components/charts/Pie';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'balanceSheetReports/getAsset' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['balanceSheetReports/getAsset']);\n  const { getAssetResponse } = useSelector(state => state.balanceSheetReports);\n  const [data] = useResponseData(getAssetResponse);\n\n  return (\n    <Card title={t('report.asset.category')} bordered={false} bodyStyle={{padding: '40px 10px'}} style={{height:'100%'}}>\n      <Pie data={data} loading={loading} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/balance-sheet/DebtCategory.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card } from \"antd\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport Pie from '@/components/charts/Pie';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  useEffect(() => {\n    dispatch({ type: 'balanceSheetReports/getDebt' });\n  }, []);\n  const loading = useSelector(state => state.loading.effects['balanceSheetReports/getDebt']);\n  const { getDebtResponse } = useSelector(state => state.balanceSheetReports);\n  const [data] = useResponseData(getDebtResponse);\n\n  return (\n    <Card title={t('report.debt.category')} bordered={false} bodyStyle={{padding: '40px 10px'}} style={{height:'100%'}}>\n      <Pie data={data} loading={loading} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/balance-sheet/index.jsx",
    "content": "import {Col, Row, Space} from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport AssetCategory from './AssetCategory'\nimport DebtCategory from './DebtCategory'\nimport t from \"@/utils/translate\";\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.report'), t('menu.balance.sheet')]} />\n      <Row gutter={8}>\n        <Col span={12}>\n          <AssetCategory />\n        </Col>\n        <Col span={12}>\n          <DebtCategory />\n        </Col>\n      </Row>\n    </Space>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/balance-sheet/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getAsset, getDebt } from '@/services/report'\nimport {query as queryCategory} from \"@/services/expense-category\";\n\nexport default modelExtend(model, {\n  namespace: 'balanceSheetReports',\n  state: {\n    getAssetResponse: undefined,\n    getDebtResponse: undefined,\n  },\n  effects: {\n    *getAsset(_, { call, put }) {\n      const response = yield call(getAsset);\n      yield put({\n        type: 'updateState',\n        payload: { getAssetResponse: response },\n      });\n    },\n    *getDebt(_, { call, put }) {\n      const response = yield call(getDebt);\n      yield put({\n        type: 'updateState',\n        payload: { getDebtResponse: response },\n      });\n    },\n  },\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-category/CategoryPie.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card } from \"antd\";\nimport {radioValueToTimeRange} from \"@/utils/util\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport Pie from '@/components/charts/Pie2';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const loading = useSelector(state => state.loading.effects['expenseCategoryReports/query']);\n  const { queryResponse } = useSelector(state => state.expenseCategoryReports);\n  const [data] = useResponseData(queryResponse);\n\n  return (\n    <Card title={t('expense.category')} bordered={false} bodyStyle={{padding: '40px 10px'}}>\n      <Pie data={data} loading={loading} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-category/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Row, Col, Button, Space, TreeSelect} from 'antd';\nimport {useCategoryTreeSelectData, useResponseData, useResponseSelectData} from \"@/utils/hooks\";\nimport { filterFormProp } from \"@/utils/var\";\nimport { searchHandler } from \"@/utils/util\";\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemCategoryReport from \"@/components/FormItemCategoryReport\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['expenseCategoryReports/query']);\n\n  useEffect(() => {\n    if (!tagResponse) dispatch({ type: 'tag/fetchExpenseable' });\n    if (!payeeResponse) dispatch({ type: 'payee/fetchExpenseable' });\n    if (!accountResponse) dispatch({ type: 'account/fetchExpenseable' });\n    if (!querySimpleResponse) dispatch({ type: 'expenseCategory/fetchSimple' });\n  }, []);\n\n  const { getExpenseableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getExpenseableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees] = useResponseSelectData(payeeResponse);\n\n  const { getExpenseableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseSelectData(accountResponse);\n\n  const { querySimpleResponse } = useSelector(state => state.expenseCategory);\n  const [categories] = useCategoryTreeSelectData(querySimpleResponse);\n\n  const [dateRadioValue, setDateRadioValue] = useState(8);\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={8}>\n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemDescriptionSearch />\n        <FormItemAccounts data={accounts} />\n        <FormItemPayees data={payees} />\n        <FormItemCategoryReport data={categories} />\n        <FormItemTags data={tags} />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-category/index.jsx",
    "content": "import { Space } from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport CategoryPie from \"./CategoryPie\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.report'), t('menu.expense.category.reports')]} />\n      <OperationBar />\n      <CategoryPie />\n    </Space>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-category/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { query as queryCategory } from '@/services/expense-category';\nimport { getExpenseCategory as getCategoryReport } from '@/services/report';\n\nexport default modelExtend(model, {\n  namespace: 'expenseCategoryReports',\n  state: {\n    getCategoryResponse: undefined,\n    queryResponse: undefined,\n  },\n  effects: {\n    *getCategory({ payload }, { call, put }) {\n      const response = yield call(queryCategory, payload);\n      yield put({\n        type: 'updateState',\n        payload: { getCategoryResponse: response },\n      });\n    },\n    *query({ payload }, { call, put }) {\n      const response = yield call(getCategoryReport, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/reports/expense-category') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-income-trend/Chart.jsx",
    "content": "import {useSelector} from \"umi\";\nimport {Card} from \"antd\";\nimport Line from '@/components/charts/Line';\nimport {useResponseData} from \"@/utils/hooks\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const loading = useSelector(state => state.loading.effects['expenseIncomeTrendReports/query']);\n  const { queryResponse } = useSelector(state => state.expenseIncomeTrendReports);\n  const [data] = useResponseData(queryResponse);\n\n  const cols = {\n    month: {\n      range: [0, 1]\n    }\n  };\n\n  return (\n    <Card title={t('menu.flow.trend')} bordered={false} bodyStyle={{padding: '40px 10px'}}>\n      <Line loading={loading} data={data} scale={cols} height={600} padding={[ 20, 20, 80, 60 ]} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-income-trend/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Row, Col, Button, Space, Divider} from 'antd';\nimport {useCategoryTreeSelectData, useResponseData, useResponseSelectData} from \"@/utils/hooks\";\nimport { filterFormProp } from \"@/utils/var\";\nimport {getNull, radioValueToTimeRange, searchHandler} from \"@/utils/util\";\nimport FormItemDateRangeWithBreak from \"@/components/FormItemDateRangeWithBreak\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemCategories from \"@/components/FormItemCategories\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['expenseIncomeTrendReports/query']);\n\n  useEffect(() => {\n    if (!expenseTagResponse) dispatch({ type: 'tag/fetchExpenseable' });\n    if (!expensePayeeResponse) dispatch({ type: 'payee/fetchExpenseable' });\n    if (!expenseAccountResponse) dispatch({ type: 'account/fetchExpenseable' });\n    if (!expenseCategoryResponse) dispatch({ type: 'incomeCategory/fetchSimple' });\n\n    if (!incomeTagResponse) dispatch({ type: 'tag/fetchIncomeable' });\n    if (!incomePayeeResponse) dispatch({ type: 'payee/fetchIncomeable' });\n    if (!incomeAccountResponse) dispatch({ type: 'account/fetchIncomeable' });\n    if (!incomeCategoryResponse) dispatch({ type: 'expenseCategory/fetchSimple' });\n  }, []);\n\n  const { getExpenseableResponse : expenseTagResponse } = useSelector(state => state.tag);\n  const [expenseTags] = useCategoryTreeSelectData(expenseTagResponse);\n\n  const { getExpenseableResponse : expensePayeeResponse } = useSelector(state => state.payee);\n  const [expensePayees] = useResponseSelectData(expensePayeeResponse);\n\n  const { getExpenseableResponse : expenseAccountResponse } = useSelector(state => state.account);\n  const [expenseAccounts] = useResponseSelectData(expenseAccountResponse);\n\n  const { querySimpleResponse: expenseCategoryResponse } = useSelector(state => state.expenseCategory);\n  const [expenseCategories] = useCategoryTreeSelectData(expenseCategoryResponse);\n\n  const { getIncomeableResponse : incomeTagResponse } = useSelector(state => state.tag);\n  const [incomeTags] = useCategoryTreeSelectData(incomeTagResponse);\n\n  const { getIncomeableResponse : incomePayeeResponse } = useSelector(state => state.payee);\n  const [incomePayees] = useResponseSelectData(incomePayeeResponse);\n\n  const { getIncomeableResponse : incomeAccountResponse } = useSelector(state => state.account);\n  const [incomeAccounts] = useResponseSelectData(incomeAccountResponse);\n\n  const { querySimpleResponse: incomeCategoryResponse } = useSelector(state => state.incomeCategory);\n  const [incomeCategories] = useCategoryTreeSelectData(incomeCategoryResponse);\n\n  const [dateRadioValue, setDateRadioValue] = useState(8);\n  function resetHandler() {\n    setDateRadioValue(8);\n    form.setFieldsValue({...initialValues});\n  }\n\n  const [initialValues, setInitialValues] = useState({});\n  useEffect(() => {\n    setInitialValues({\n      'createTimeRange': radioValueToTimeRange(dateRadioValue),\n      'breakType': \"month\"\n    });\n    searchHandler(form);\n  }, []);\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <FormItemDateRangeWithBreak form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n      <Divider orientation=\"left\" plain style={{marginTop:0}}>{t('report.expense.condition')}</Divider>\n      <Row gutter={8}>\n        <FormItemAccounts data={expenseAccounts} name=\"expenseAccounts\" />\n        <FormItemPayees data={expensePayees} name=\"expensePayees\" />\n        <FormItemCategories data={expenseCategories} name=\"expenseCategories\" />\n        <FormItemTags data={expenseTags} name=\"expenseTags\" />\n      </Row>\n      <Divider orientation=\"left\" plain style={{marginTop:0}}>{t('report.income.condition')}</Divider>\n      <Row gutter={8}>\n        <FormItemAccounts data={incomeAccounts} name=\"incomeAccounts\" />\n        <FormItemPayees data={incomePayees} name=\"incomePayees\" />\n        <FormItemCategories data={incomeCategories} name=\"incomeCategories\" />\n        <FormItemTags data={incomeTags} name=\"incomeTags\" />\n      </Row>\n      <Row>\n        <Col flex=\"auto\" style={{ textAlign:'right' }}>\n          <Space>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-income-trend/index.jsx",
    "content": "import { Space } from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport Chart from './Chart';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.report'), t('menu.flow.trend')]} />\n      <OperationBar />\n      <Chart />\n    </Space>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-income-trend/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getExpenseIncomeTrend } from \"@/services/report\";\n\nexport default modelExtend(model, {\n  namespace: 'expenseIncomeTrendReports',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(getExpenseIncomeTrend, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/reports/expense-income-trend') {\n          console.log(query);\n          if (query && query.breakType) {\n            dispatch({ type: 'query', payload: query });\n          }\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-tag/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Row, Col, Button, Space} from 'antd';\nimport {useCategoryTreeSelectData, useResponseData, useResponseSelectData} from \"@/utils/hooks\";\nimport { filterFormProp } from \"@/utils/var\";\nimport { searchHandlerWithCategory } from \"@/utils/util\";\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemCategories from \"@/components/FormItemCategories\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemTagReport from \"@/components/FormItemTagReport\";\nimport t from '@/utils/translate';\nimport Pie from \"@/components/charts/Pie2\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['expenseTagReports/query']);\n\n  useEffect(() => {\n    if (!tagResponse) dispatch({ type: 'tag/fetchExpenseable' });\n    if (!payeeResponse) dispatch({ type: 'payee/fetchExpenseable' });\n    if (!accountResponse) dispatch({ type: 'account/fetchExpenseable' });\n    if (!querySimpleResponse) dispatch({ type: 'expenseCategory/fetchSimple' });\n  }, []);\n\n  const { getExpenseableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getExpenseableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees] = useResponseSelectData(payeeResponse);\n\n  const { getExpenseableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseSelectData(accountResponse);\n\n  const { querySimpleResponse } = useSelector(state => state.expenseCategory);\n  const [categories] = useCategoryTreeSelectData(querySimpleResponse);\n\n  const [dateRadioValue, setDateRadioValue] = useState(8);\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={8}>\n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemDescriptionSearch />\n        <FormItemAccounts data={accounts} />\n        <FormItemPayees data={payees} />\n        <FormItemCategories data={categories} />\n        <FormItemTagReport data={tags} />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandlerWithCategory(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-tag/TagPie.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card } from \"antd\";\nimport {radioValueToTimeRange} from \"@/utils/util\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport Pie from '@/components/charts/Pie2';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const loading = useSelector(state => state.loading.effects['expenseTagReports/query']);\n  const { queryResponse } = useSelector(state => state.expenseTagReports);\n  const [data] = useResponseData(queryResponse);\n\n  return (\n    <Card title={t('expense.tag')} bordered={false} bodyStyle={{padding: '40px 10px'}}>\n      <Pie data={data} loading={loading} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-tag/index.jsx",
    "content": "import { Space } from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport TagPie from \"./TagPie\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.report'), t('menu.expense.tag.reports')]} />\n      <OperationBar />\n      <TagPie />\n    </Space>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/expense-tag/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getExpenseTag as getTagReport } from '@/services/report';\n\nexport default modelExtend(model, {\n  namespace: 'expenseTagReports',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(getTagReport, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/reports/expense-tag') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/income-category/CategoryPie.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card } from \"antd\";\nimport {radioValueToTimeRange} from \"@/utils/util\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport Pie from '@/components/charts/Pie2';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const loading = useSelector(state => state.loading.effects['incomeCategoryReports/query']);\n  const { queryResponse } = useSelector(state => state.incomeCategoryReports);\n  const [data] = useResponseData(queryResponse);\n\n  return (\n    <Card title={t('income.category')} bordered={false} bodyStyle={{padding: '40px 10px'}}>\n      <Pie data={data} loading={loading} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/income-category/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Row, Col, Button, Select, Space, Checkbox, Input} from 'antd';\nimport {useCategoryTreeSelectData, useResponseData, useResponseSelectData} from \"@/utils/hooks\";\nimport { filterFormProp } from \"@/utils/var\";\nimport { searchHandler } from \"@/utils/util\";\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemCategoryReport from \"@/components/FormItemCategoryReport\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport t from '@/utils/translate';\nimport Pie from \"@/components/charts/Pie2\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['incomeCategoryReports/query']);\n\n  useEffect(() => {\n    if (!tagResponse) dispatch({ type: 'tag/fetchIncomeable' });\n    if (!payeeResponse) dispatch({ type: 'payee/fetchIncomeable' });\n    if (!accountResponse) dispatch({ type: 'account/fetchIncomeable' });\n    if (!querySimpleResponse) dispatch({ type: 'incomeCategory/fetchSimple' });\n  }, []);\n\n  const { getIncomeableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getIncomeableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees] = useResponseSelectData(payeeResponse);\n\n  const { getIncomeableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseSelectData(accountResponse);\n\n  const { querySimpleResponse } = useSelector(state => state.incomeCategory);\n  const [categories] = useCategoryTreeSelectData(querySimpleResponse);\n\n  const [dateRadioValue, setDateRadioValue] = useState(8);\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={8}>\n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemDescriptionSearch />\n        <FormItemAccounts data={accounts} />\n        <FormItemPayees data={payees} />\n        <FormItemCategoryReport data={categories} />\n        <FormItemTags data={tags} />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandler(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/income-category/index.jsx",
    "content": "import {Col, Row, Space} from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport CategoryPie from \"./CategoryPie\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.report'), t('menu.expense.category.reports')]} />\n      <OperationBar />\n      <CategoryPie />\n    </Space>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/income-category/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { query as queryCategory } from '@/services/income-category';\nimport { getIncomeCategory as getCategoryReport } from '@/services/report';\n\nexport default modelExtend(model, {\n  namespace: 'incomeCategoryReports',\n  state: {\n    getCategoryResponse: undefined,\n    queryResponse: undefined,\n  },\n  effects: {\n    *getCategory({ payload }, { call, put }) {\n      const response = yield call(queryCategory, payload);\n      yield put({\n        type: 'updateState',\n        payload: { getCategoryResponse: response },\n      });\n    },\n    *query({ payload }, { call, put }) {\n      const response = yield call(getCategoryReport, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/reports/income-category') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/income-tag/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Row, Col, Button, Space} from 'antd';\nimport {useCategoryTreeSelectData, useResponseData, useResponseSelectData} from \"@/utils/hooks\";\nimport { filterFormProp } from \"@/utils/var\";\nimport { searchHandlerWithCategory } from \"@/utils/util\";\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemCategories from \"@/components/FormItemCategories\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport FormItemPayees from \"@/components/FormItemPayees\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['incomeTagReports/query']);\n\n  useEffect(() => {\n    if (!tagResponse) dispatch({ type: 'tag/fetchIncomeable' });\n    if (!payeeResponse) dispatch({ type: 'payee/fetchIncomeable' });\n    if (!accountResponse) dispatch({ type: 'account/fetchIncomeable' });\n    if (!querySimpleResponse) dispatch({ type: 'incomeCategory/fetchSimple' });\n  }, []);\n\n  const { getIncomeableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  const { getIncomeableResponse : payeeResponse } = useSelector(state => state.payee);\n  const [payees] = useResponseSelectData(payeeResponse);\n\n  const { getIncomeableResponse : accountResponse } = useSelector(state => state.account);\n  const [accounts] = useResponseSelectData(accountResponse);\n\n  const { querySimpleResponse } = useSelector(state => state.incomeCategory);\n  const [categories] = useCategoryTreeSelectData(querySimpleResponse);\n\n  const [dateRadioValue, setDateRadioValue] = useState(8);\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={8}>\n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemDescriptionSearch />\n        <FormItemAccounts data={accounts} />\n        <FormItemPayees data={payees} />\n        <FormItemCategories data={categories} />\n        <FormItemTags data={tags} />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandlerWithCategory(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/income-tag/TagPie.jsx",
    "content": "import {useEffect, useState} from \"react\";\nimport {useDispatch, useSelector} from \"umi\";\nimport { Card } from \"antd\";\nimport {radioValueToTimeRange} from \"@/utils/util\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport Pie from '@/components/charts/Pie2';\nimport t from '@/utils/translate';\n\nexport default () => {\n\n  const loading = useSelector(state => state.loading.effects['incomeTagReports/query']);\n  const { queryResponse } = useSelector(state => state.incomeTagReports);\n  const [data] = useResponseData(queryResponse);\n\n  return (\n    <Card title={t('income.tag')} bordered={false} bodyStyle={{padding: '40px 10px'}}>\n      <Pie data={data} loading={loading} />\n    </Card>\n  )\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/income-tag/index.jsx",
    "content": "import { Space } from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from \"@/components/Breadcrumb\";\nimport OperationBar from './OperationBar';\nimport TagPie from \"./TagPie\";\nimport t from \"@/utils/translate\";\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.report'), t('menu.income.tag.reports')]} />\n      <OperationBar />\n      <TagPie />\n    </Space>\n  )\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/reports/income-tag/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { getIncomeTag as getTagReport } from '@/services/report';\n\nexport default modelExtend(model, {\n  namespace: 'incomeTagReports',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(getTagReport, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/reports/income-tag') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/scheduled/index.jsx",
    "content": "export default () => {\n  return (\n    <div>scheduled</div>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/settings/index.jsx",
    "content": "export default () => {\n  return (\n    <div>settings</div>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/signin/RememberDropDown.js",
    "content": "// not used\nimport { Menu, Dropdown } from 'antd';\nimport { DownOutlined } from '@ant-design/icons';\n\nconst menu = (\n  <Menu>\n    <Menu.Item>不记住登录状态</Menu.Item>\n    <Menu.Item>记住30分钟</Menu.Item>\n    <Menu.Item>记住1小时</Menu.Item>\n    <Menu.Item>记住1天</Menu.Item>\n    <Menu.Item>记住3天</Menu.Item>\n    <Menu.Item>记住7天</Menu.Item>\n    <Menu.Item>记住30天</Menu.Item>\n  </Menu>\n);\n\nconst RememberDropDown = () => {\n  return (\n    <Dropdown overlay={menu}>\n      <span className=\"ant-dropdown-link\" style={{cursor: 'pointer'}}>\n        不记住登录状态 <DownOutlined />\n      </span>\n    </Dropdown>\n  );\n};\n\nexport default RememberDropDown;"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/signin/index.jsx",
    "content": "import { useEffect } from 'react';\nimport { Form, Input, Checkbox, Button, message } from 'antd';\nimport { Link, history, useDispatch, useSelector } from 'umi';\nimport { UserOutlined, LockOutlined } from '@ant-design/icons';\nimport { userNameRules, passwordRules }  from '@/utils/rules';\nimport t from '@/utils/translate';\n\nimport styles from './index.less';\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const { signInResponse } = useSelector(state => state.userSignIn);\n  const submitting = useSelector(state => state.loading.effects['userSignIn/submit']);\n\n  const messageLoginSuccess = t('signin.success');\n  useEffect(() => {\n    if (signInResponse && signInResponse.success) {\n      message.success(messageLoginSuccess);\n      history.push({ pathname: '/dashboard' });\n    }\n    return () => {\n      dispatch({ type: 'userSignIn/clearSignInResponse' });\n    }\n  }, [signInResponse]);\n\n  const handleSubmit = (values) => {\n    dispatch({ type: \"userSignIn/submit\", payload: { ...values } });\n  };\n\n  return (\n    <div className={styles.main}>\n      <Form size=\"large\" onFinish={ handleSubmit }>\n        <Form.Item name=\"userName\" rules={ userNameRules() }>\n          <Input prefix={<UserOutlined className=\"site-form-item-icon\" />} placeholder={t('placeholder.userName')} />\n        </Form.Item>\n        <Form.Item name=\"password\" rules={ passwordRules() }>\n          <Input prefix={<LockOutlined className=\"site-form-item-icon\" />} type=\"password\" placeholder={t('placeholder.password')} />\n        </Form.Item>\n        <div className={styles.form_row}>\n          <Form.Item name=\"remember\" valuePropName=\"checked\" noStyle>\n            <Checkbox>{t('signin.keep')}</Checkbox>\n          </Form.Item>\n          {/*<Link to=\"/register\" style={{float: 'right'}}>{t('signin.forget')}</Link>*/}\n        </div>\n        <div className={styles.form_row}>\n          <Button size=\"large\" type=\"primary\" htmlType=\"submit\" block loading={ submitting }>{t('signin')}</Button>\n        </div>\n        <div className={styles.form_row} style={{textAlign: \"center\"}}>\n          <Link to=\"/register\">{t('register')}</Link>\n        </div>\n      </Form>\n    </div>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/signin/index.less",
    "content": "@import '~antd/es/style/themes/default.less';\n\n.main {\n  margin: 0 auto;\n  width: 368px;\n  @media screen and (max-width: @screen-sm) {\n    width: 95%;\n  }\n}\n.form_row {\n  margin-bottom: 15px;\n}"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/signin/model.js",
    "content": "import { signin } from '@/services/user';\n\nexport default {\n  namespace: 'userSignIn',\n  state: {\n    signInResponse: undefined,\n  },\n  effects: {\n    *submit({ payload }, { put, call }) {\n      const response = yield call(signin, payload);\n      yield put({\n        type: 'signInHandler',\n        payload: response,\n      });\n    }\n  },\n  reducers: {\n    signInHandler(state, { payload }) {\n      return { ...state, signInResponse: payload };\n    },\n    clearSignInResponse(state) {\n      return { ...state, signInResponse: undefined };\n    }\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/signin') {\n          // token有可能失效，导致死循环跳转。\n          // if (localStorage.getItem(\"userToken\")) {\n          //   history.push({ pathname: '/dashboard' });\n          // }\n        }\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/test/index.jsx",
    "content": "import request from 'umi-request';\n\nexport default class extends React.Component {\n  render() {\n    return (\n      <>\n        123\n      </>\n    )\n  }\n}\n\n\n\n\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/transfers/OperationBar.jsx",
    "content": "import { useState, useEffect } from 'react';\nimport { useDispatch, useSelector } from 'umi';\nimport {Form, Row, Col, Button, Space} from 'antd';\nimport {useCategoryTreeSelectData, useResponseSelectData} from \"@/utils/hooks\";\nimport { filterFormProp } from \"@/utils/var\";\nimport {searchHandlerWithCategory} from \"@/utils/util\";\nimport {showFlowModal} from '@/utils/flow';\nimport FormItemDateRange from \"@/components/FormItemDateRange\";\nimport FormItemAmountRange from \"@/components/FormItemAmountRange\";\nimport FormItemDescriptionSearch from \"@/components/FormItemDescriptionSearch\";\nimport FormItemAccounts from \"@/components/FormItemAccounts\";\nimport FormItemTags from \"@/components/FormItemTags\";\nimport FormItemStatus from \"@/components/FormItemStatus\";\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n\n  const dispatch = useDispatch();\n  const [form] = Form.useForm();\n  const loading = useSelector(state => state.loading.effects['transfers/query']);\n\n  useEffect(() => {\n    if (!getTransferToAbleResponse) dispatch({ type: 'account/fetchTransferToAble' });\n    if (!getTransferFromAbleResponse) dispatch({ type: 'account/fetchTransferFromAble' });\n    if (!tagResponse) dispatch({ type: 'tag/fetchTransferable' });\n  }, []);\n\n  const { getTransferToAbleResponse } = useSelector(state => state.account);\n  const [accountsTransferTo] = useResponseSelectData(getTransferToAbleResponse);\n\n  const { getTransferFromAbleResponse } = useSelector(state => state.account);\n  const [accountsTransferFrom] = useResponseSelectData(getTransferFromAbleResponse);\n\n  const { getTransferableResponse : tagResponse } = useSelector(state => state.tag);\n  const [tags] = useCategoryTreeSelectData(tagResponse);\n\n  function addHandler() {\n    showFlowModal(3, 1, {});\n  }\n\n  const [dateRadioValue, setDateRadioValue] = useState();\n  function resetHandler() {\n    setDateRadioValue();\n    form.resetFields();\n  }\n\n  return (\n    <Form {...filterFormProp} form={form}>\n      <Row gutter={20}>\n        <FormItemDateRange form={form} dateRadioValue={dateRadioValue} setDateRadioValue={setDateRadioValue} />\n        <FormItemAmountRange />\n        <FormItemDescriptionSearch />\n        <FormItemAccounts data={accountsTransferFrom} label={t('transfer.from.account')} name=\"fromAccounts\" />\n        <FormItemAccounts data={accountsTransferTo} label={t('transfer.to.account')} name=\"toAccounts\" />\n        <FormItemStatus />\n        <FormItemTags data={tags} />\n        <Col flex=\"100px\" style={{ marginLeft: 'auto' }}>\n          <Space>\n            <Button type=\"primary\" onClick={addHandler}>{t('new')}</Button>\n            <Button type=\"primary\" loading={loading} onClick={()=>searchHandlerWithCategory(form)}>{t('search')}</Button>\n            <Button type=\"primary\" onClick={resetHandler}>{t('form.reset')}</Button>\n          </Space>\n        </Col>\n      </Row>\n    </Form>\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/transfers/RecordTable.jsx",
    "content": "import { history, useDispatch, useSelector } from 'umi';\nimport {Table, Space, message, Modal, Tag, Descriptions, Dropdown, Menu } from 'antd';\nimport {DownOutlined} from \"@ant-design/icons\";\nimport { remove, confirm } from '@/services/flow';\nimport {useDescriptionEnable, useImageEnable, useResultPaginationAndData, useTimeFormat} from '@/utils/hooks';\nimport { handleTableChange } from \"@/utils/util\";\nimport {imageHandler, showFlowModal} from '@/utils/flow';\nimport { tableProp } from '@/utils/var';\nimport {createTimeCol, amountCol, statusCol} from '@/utils/columns';\nimport t from \"@/utils/translate\";\n\nexport default () => {\n\n  const dispatch = useDispatch();\n\n  const { queryResponse } = useSelector(state => state.transfers);\n  const queryLoading = useSelector(state => state.loading.effects['transfers/query']);\n  const [ dataAndPagination ] = useResultPaginationAndData(queryResponse);\n  const imageEnable = useImageEnable();\n  const descriptionEnable = useDescriptionEnable();\n  const timeFormat = useTimeFormat();\n\n  let columns = [];\n  if (descriptionEnable) {\n    columns.push({\n      title: t('description'),\n      dataIndex: 'description',\n    });\n  }\n  columns = columns.concat(\n    [\n      amountCol(),\n      createTimeCol(timeFormat),\n      {\n        title: t('account'),\n        dataIndex: 'accountName',\n      },\n      {\n        title: t('flow.tag'),\n        dataIndex: 'tags',\n        render: tags => <>{tags.map(i => <Tag color=\"blue\" key={i.tagId}>{i.tagName}</Tag>)}</>\n      },\n      statusCol(),\n      {\n        title: t('operation'),\n        key: 'operation',\n        width: 100,\n        align: 'center',\n        render: (_, record) => {\n          return (\n            <Space size=\"small\">\n              <a onClick={() => copyHandler(record)}>{t('copy')}</a>\n              <Dropdown overlay={\n                <Menu>\n                  {imageEnable ? <Menu.Item onClick={() => imageHandler(record)}>{t('image')}</Menu.Item> : null}\n                  <Menu.Item onClick={() => updateHandler(record)}>{t('update')}</Menu.Item>\n                  <Menu.Item disabled={record.status !== 2} onClick={() => confirmHandler(record)}>{t('confirm')}</Menu.Item>\n                  <Menu.Item onClick={() => deleteHandler(record)}>{t('delete')}</Menu.Item>\n                </Menu>\n              }>\n                <a>\n                  {t('more')} <DownOutlined />\n                </a>\n              </Dropdown>\n            </Space>\n          )\n        }\n      }\n    ]\n  );\n\n  function copyHandler(record) {\n    showFlowModal(3, 3, {...record});\n  }\n\n  const updateHandler = (record) => {\n    showFlowModal(3, 2, {...record});\n  }\n\n  const messageDeleteConfirm = t('delete.confirm', { name: '' });\n  const messageDeleteConfirmBalance = t('delete.confirm.balance');\n  const messageOperationSuccess = t('operation.success');\n  function deleteHandler(record) {\n    Modal.confirm({\n      title: record.status === 2 ? messageDeleteConfirm : messageDeleteConfirmBalance,\n      onOk: async () => {\n        const response = await remove(record);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'transfers/query', payload: history.location.query });\n        }\n      }\n    });\n  }\n\n  const messageConfirmOperation = t('confirm.operation');\n  function confirmHandler(record) {\n    Modal.confirm({\n      title: messageConfirmOperation,\n      onOk: async () => {\n        const response = await confirm(record);\n        if (response && response.success) {\n          message.success(messageOperationSuccess);\n          dispatch({ type: 'transfers/query', payload: history.location.query });\n        }\n      }\n    });\n  }\n\n  return (\n    <Table\n      {...tableProp}\n      columns={columns}\n      expandable={{\n        expandedRowRender: record => <Descriptions size=\"small\" bordered>\n          {record.needConvert ? <Descriptions.Item label={t('convertCurrency')+record.toCurrencyCode}>{record.convertedAmount}</Descriptions.Item> : null}\n          {record.notes ? <Descriptions.Item label={t('notes')}>{record.notes}</Descriptions.Item> : null}\n        </Descriptions>,\n        rowExpandable: record => record.notes || record.needConvert,\n      }}\n      dataSource={dataAndPagination.data}\n      pagination={dataAndPagination.pagination}\n      loading={queryLoading}\n      onChange={handleTableChange}\n    />\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/transfers/TotalAlert.jsx",
    "content": "import {useMemo} from \"react\";\nimport { useSelector } from 'umi';\nimport AlertTotalSearch from \"@/components/AlertTotalSearch\";\nimport {useResponseData} from \"@/utils/hooks\";\nimport t from '@/utils/translate';\n\n\nexport default () => {\n\n  const { queryResponse } = useSelector(state => state.transfers);\n  const [ responseData ] = useResponseData(queryResponse);\n  const total = useMemo(() => {\n    if (responseData.result) {\n      return responseData.total;\n    } else {\n      return 0;\n    }\n  }, [responseData]);\n\n  return (\n    <AlertTotalSearch message={t('gross.amount') + ': ' + total} />\n  );\n\n};\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/transfers/index.jsx",
    "content": "import { Space } from \"antd\";\nimport {spaceVProp} from \"@/utils/var\";\nimport Breadcrumb from '@/components/Breadcrumb';\nimport OperationBar from './OperationBar';\nimport RecordTable from './RecordTable';\nimport TotalAlert from './TotalAlert';\nimport t from \"@/utils/translate\";\n\n\nexport default () => {\n  return (\n    <Space {...spaceVProp}>\n      <Breadcrumb data={[t('menu.bookkeeping'), t('menu.transfer')]} />\n      <OperationBar />\n      <TotalAlert />\n      <RecordTable />\n    </Space>\n  );\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/pages/transfers/model.js",
    "content": "import modelExtend from 'dva-model-extend';\nimport {model} from '@/utils/model';\nimport { query } from '@/services/transfer';\n\nexport default modelExtend(model, {\n  namespace: 'transfers',\n  state: {\n    queryResponse: undefined,\n  },\n  effects: {\n    *query({ payload }, { call, put }) {\n      const response = yield call(query, payload);\n      yield put({\n        type: 'updateState',\n        payload: { queryResponse: response },\n      });\n    },\n  },\n  subscriptions: {\n    setup({ dispatch, history }) {\n      return history.listen(({ pathname, query }) => {\n        if (pathname === '/transfers') {\n          dispatch({ type: 'query', payload: query });\n        }\n      });\n    }\n  }\n})\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/account.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'accounts';\n\n// 搜索流水，按账户查找的下拉框\nexport async function getEnable() {\n  return request(prefix + '/enable', {\n    method: 'GET',\n  });\n}\n\nexport async function getExpenseable() {\n  return request(prefix + '/expenseable', {\n    method: 'GET',\n  });\n}\n\nexport async function getIncomeable() {\n  return request(prefix + '/incomeable', {\n    method: 'GET',\n  });\n}\n\nexport async function getTransferFromAble() {\n  return request(prefix + '/transfer-from-able', {\n    method: 'GET',\n  });\n}\n\nexport async function getTransferToAble() {\n  return request(prefix + '/transfer-to-able', {\n    method: 'GET',\n  });\n}\n\nexport async function adjustBalance(id, json) {\n  return request(prefix + '/' + id + '/adjust-balance', {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n\nexport async function toggle(id) {\n  return request(prefix + '/' + id + '/toggle', {\n    method: 'PUT',\n  });\n}\n\nexport async function toggleInclude(id) {\n  return request(prefix + '/' + id + '/toggleInclude', {\n    method: 'PUT',\n  });\n}\n\nexport async function toggleExpenseable(id) {\n  return request(prefix + '/' + id + '/toggleExpenseable', {\n    method: 'PUT',\n  });\n}\n\nexport async function toggleIncomeable(id) {\n  return request(prefix + '/' + id + '/toggleIncomeable', {\n    method: 'PUT',\n  });\n}\n\nexport async function toggleTransferFromAble(id) {\n  return request(prefix + '/' + id + '/toggleTransferFromAble', {\n    method: 'PUT',\n  });\n}\n\nexport async function toggleTransferToAble(id) {\n  return request(prefix + '/' + id + '/toggleTransferToAble', {\n    method: 'PUT',\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix+'/'+id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/adjust-balance.js",
    "content": "import request from '@/utils/request';\n\nconst prefix = 'adjust-balances';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/'+id, {\n    method: 'DELETE',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/asset-account.js",
    "content": "import request from '@/utils/request';\n\nconst prefix = 'asset-accounts';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function sum() {\n  return request(prefix+'/sum', {\n    method: 'GET',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/book.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'books';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function config(_, json) {\n  return request(prefix + '/config', {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n\nexport async function toggle(id) {\n  return request(prefix + '/' + id + '/toggle', {\n    method: 'PUT',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/category.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'categories';\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n\nexport async function toggle(id) {\n  return request(prefix + '/' + id + '/toggle', {\n    method: 'PUT',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/checking-account.js",
    "content": "import request from '@/utils/request';\n\nconst prefix = 'checking-accounts';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function sum() {\n  return request(prefix+'/sum', {\n    method: 'GET',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/credit-account.js",
    "content": "import request from '@/utils/request';\n\nconst prefix = 'credit-accounts';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function sum() {\n  return request(prefix+'/sum', {\n    method: 'GET',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/currency.js",
    "content": "import request from '@/utils/request';\n\nconst prefix = 'currency';\n\nexport async function getAll() {\n  return request(prefix + '/all', {\n    method: 'GET'\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/dashboard.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'dashboard';\n\nexport async function getAssetOverview() {\n  return request(prefix + '/asset-overview', {\n    method: 'GET',\n  });\n}\n\nexport async function getExpenseIncomeTable() {\n  return request(prefix + '/expense-income-table', {\n    method: 'GET',\n  });\n}\n\nexport async function getExpenseTrend() {\n  return request(prefix + '/expense-trend', {\n    method: 'GET',\n  });\n}\n\nexport async function getIncomeTrend() {\n  return request(prefix + '/income-trend', {\n    method: 'GET',\n  });\n}\n\nexport async function getExpenseCategory(params) {\n  return request(prefix + '/expense-category', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getIncomeCategory(params) {\n  return request(prefix + '/income-category', {\n    method: 'GET',\n    params: params,\n  });\n}\n\n\n\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/deal.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'deals';\n\nexport async function getRefunds(id) {\n  return request(prefix + '/' + id + '/refunds', {\n    method: 'GET',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/debt-account.js",
    "content": "import request from '@/utils/request';\n\nconst prefix = 'debt-accounts';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function sum() {\n  return request(prefix+'/sum', {\n    method: 'GET',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/expense-category.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'expense-categories';\n\n// 给分类管理的table用\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\n// 给类别下拉选择框使用\nexport async function querySimple(params) {\n  return request(prefix + '/enable', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/expense.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'expenses';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix+'/'+id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function refund(id, json) {\n  return request(prefix + '/' + id + '/refund', {\n    method: 'POST',\n    data: json,\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/flow-image.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'flow-images';\n\nexport async function getUploadToken() {\n  return request(prefix + '/upload-token', {\n    method: 'GET',\n  });\n}\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/flow.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'flows';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function remove(record) {\n  return request(prefix + '/' + record.id, {\n    method: 'DELETE',\n  });\n}\n\nexport async function confirm(record) {\n  return request(prefix + '/' + record.id + '/confirm', {\n    method: 'PUT',\n  });\n}\n\nexport async function audit(params) {\n  return request(prefix + '/audit', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getImages(id) {\n  return request(prefix + '/' + id + '/images', {\n    method: 'GET',\n  });\n}\n\nexport async function updateImages(id, images) {\n  return request(prefix + '/' + id + '/images', {\n    method: 'POST',\n    data: images,\n  });\n}\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/group.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'groups';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getEnable() {\n  return request(prefix + '/enable', {\n    method: 'GET'\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/income-category.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'income-categories';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function querySimple(params) {\n  return request(prefix + '/enable', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/income.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'incomes';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix+'/'+id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function refund(id, json) {\n  return request(prefix + '/' + id + '/refund', {\n    method: 'POST',\n    data: json,\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/item.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'items';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function run(id, json) {\n  return request(prefix + '/' + id + '/run', {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function recall(id, json) {\n  return request(prefix + '/' + id + '/recall', {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/log.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'logs';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/payee.js",
    "content": "import request from '@/utils/request';\n\nconst prefix = 'payees';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getEnable() {\n  return request(prefix + '/enable', {\n    method: 'GET'\n  });\n}\n\nexport async function getExpenseable() {\n  return request(prefix + '/expenseable', {\n    method: 'GET'\n  });\n}\n\nexport async function getIncomeable() {\n  return request(prefix + '/incomeable', {\n    method: 'GET'\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n\nexport async function toggle(id) {\n  return request(prefix + '/' + id + '/toggle', {\n    method: 'PUT',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/report.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'reports';\n\nexport async function getExpenseCategory(params) {\n  return request(prefix + '/expense-category', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getExpenseTag(params) {\n  return request(prefix + '/expense-tag', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getIncomeCategory(params) {\n  return request(prefix + '/income-category', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getIncomeTag(params) {\n  return request(prefix + '/income-tag', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getExpenseIncomeTrend(params) {\n  return request(prefix + '/expense-income-trend', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getAssetDebtTrend(params) {\n  return request(prefix + '/asset-debt-trend', {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getAsset() {\n  return request(prefix + '/asset', {\n    method: 'GET',\n  });\n}\n\nexport async function getDebt() {\n  return request(prefix + '/debt', {\n    method: 'GET',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/schedule.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'schedules';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/tag-relation.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'tag-relations';\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/tag.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'tags';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function getEnable() {\n  return request(prefix + '/enable', {\n    method: 'GET'\n  });\n}\n\nexport async function getExpenseable() {\n  return request(prefix + '/expenseable', {\n    method: 'GET'\n  });\n}\n\nexport async function getIncomeable() {\n  return request(prefix + '/incomeable', {\n    method: 'GET'\n  });\n}\n\nexport async function getTransferable() {\n  return request(prefix + '/transferable', {\n    method: 'GET'\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix + '/' + id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n\nexport async function remove(id) {\n  return request(prefix + '/' + id, {\n    method: 'DELETE',\n  });\n}\n\nexport async function toggle(id) {\n  return request(prefix + '/' + id +'/toggle', {\n    method: 'PUT',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/transfer.js",
    "content": "import request from \"@/utils/request\";\n\nconst prefix = 'transfers';\n\nexport async function query(params) {\n  return request(prefix, {\n    method: 'GET',\n    params: params,\n  });\n}\n\nexport async function create(json) {\n  return request(prefix, {\n    method: 'POST',\n    data: json,\n  });\n}\n\nexport async function update(id, json) {\n  return request(prefix+'/'+id, {\n    method: 'PUT',\n    data: json,\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/services/user.js",
    "content": "import request from \"@/utils/request\";\n\nexport async function register(params) {\n  return request('register', {\n    method: 'POST',\n    data: params,\n  });\n}\n\nexport async function signin(params) {\n  return request('signin', {\n    method: 'POST',\n    data: params,\n  });\n}\n\nexport async function signout() {\n  return request('signout', {\n    method: 'POST',\n  });\n}\n\nexport async function updatePassword(params) {\n  return request('updatePassword', {\n    method: 'PUT',\n    data: params,\n  });\n}\n\nexport async function getSessionUser() {\n  return request('session', {\n    method: 'GET',\n  });\n}\n\nexport async function setDefaultBook(id) {\n  return request('setDefaultBook/' + id, {\n    method: 'PUT',\n  });\n}\n\nexport async function setDefaultGroup(id) {\n  return request('setDefaultGroup/' + id, {\n    method: 'PUT',\n  });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/columns.js",
    "content": "import {Switch} from \"antd\";\nimport moment from 'moment';\nimport FlagTag from '@/components/FlagTag';\nimport FlowStatusDisplay from '@/components/FlowStatusDisplay';\nimport t from '@/utils/translate';\n\nexport const createTimeCol = (timeFormat) => {\n  return {\n    title: t('flow.createTime'),\n    dataIndex: 'createTime',\n    sorter: true,\n    width: 125,\n    align: 'center',\n    render: time => moment(time).format(timeFormat ? timeFormat : 'YYYY-MM-DD')\n  }\n}\n\nexport const amountCol = () => {\n  return {\n    title: t('amount'),\n    dataIndex: 'amount',\n    sorter: true,\n    width: 70,\n  }\n}\n\nexport const balanceCol = () => {\n  return {\n    title:  t('account.balance'),\n    dataIndex: 'balance',\n    sorter: true,\n    width: 80,\n  }\n}\n\nexport const statusCol = () => {\n  return {\n    title: t('flow.status'),\n    dataIndex: 'status',\n    sorter: true,\n    width: 70,\n    align: 'center',\n    render: (_, record) => <FlowStatusDisplay data={record} />\n  }\n}\n\nexport const flowTypeCol = () => {\n  return {\n    title: t('flow.type'),\n    dataIndex: 'typeName',\n    // sorter: true,\n    width: 58,\n  }\n}\n\nexport const accountIncludeCol = (toggleIncludeHandler) => {\n  return {\n    title: t('account.include'),\n    dataIndex: 'include',\n    sorter: true,\n    width: 90,\n    render: (value, record) => <Switch onChange={() => toggleIncludeHandler(record)} checked={value} />\n  }\n}\n\nexport const accountExpenseableCol = (toggleExpenseableHandler) => {\n  return {\n    title: t('expenseable'),\n    dataIndex: 'expenseable',\n    sorter: true,\n    width: 70,\n    render: (value, record) => <Switch onChange={() => toggleExpenseableHandler(record)} checked={value} />\n  }\n}\n\nexport const accountIncomeableCol = (toggleIncomeableHandler) => {\n  return {\n    title: t('incomeable'),\n    dataIndex: 'incomeable',\n    sorter: true,\n    width: 70,\n    render: (value, record) => <Switch onChange={() => toggleIncomeableHandler(record)} checked={value} />\n  }\n}\n\nexport const accountTransferToCol = (toggleTransferFromAbleHandler) => {\n  return {\n    title: t('transferToAble'),\n    dataIndex: 'transferToAble',\n    sorter: true,\n    width: 70,\n    render: (value, record) => <Switch onChange={() => toggleTransferFromAbleHandler(record)} checked={value} />\n  }\n}\n\nexport const accountTransferFromCol = (toggleTransferToAbleHandler) => {\n  return {\n    title: t('transferFromAble'),\n    dataIndex: 'transferFromAble',\n    sorter: true,\n    width: 70,\n    render: (value, record) => <Switch onChange={() => toggleTransferToAbleHandler(record)} checked={value} />\n  }\n}\n\nexport const accountEnableCol = (toggleHandler) => {\n  return {\n    title: t('is.enable'),\n    dataIndex: 'enable',\n    sorter: true,\n    width: 80,\n    render: (value, record) => <Switch onChange={() => toggleHandler(record)} checked={value} />\n  }\n}\n\nexport const categoryExpenseableCol = () => {\n  return {\n    title: t('expenseable'),\n    dataIndex: 'expenseable',\n    sorter: true,\n    width: 120,\n    render: value => <FlagTag value={value} />\n  }\n}\n\nexport const categoryIncomeableCol = () => {\n  return {\n    title: t('incomeable'),\n    dataIndex: 'incomeable',\n    sorter: true,\n    width: 120,\n    render: value => <FlagTag value={value} />\n  }\n}\n\nexport const categoryTransferableCol = () => {\n  return {\n    title: t('transferable'),\n    dataIndex: 'transferable',\n    sorter: true,\n    width: 120,\n    render: value => <FlagTag value={value} />\n  }\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/flow.js",
    "content": "import {getDvaApp, useDispatch} from \"umi\";\nimport ExpenseModal from '@/components/ExpenseModal';\nimport IncomeModal from '@/components/IncomeModal';\nimport TransferModal from '@/components/TransferModal';\nimport AdjustBalanceModal from '@/components/AdjustBalanceModal';\nimport t from \"@/utils/translate\";\nimport FlowImageUploadModal from \"@/components/FlowImageUploadModal\";\n\nexport function showFlowModal(flowType, modalType, currentItem) {\n  const dispatch = getDvaApp()._store.dispatch;\n  switch (flowType) {\n    case 1:\n      dispatch({ type: 'modal/show', payload: {component: ExpenseModal, type: modalType, currentItem: currentItem }});\n      break;\n    case 2:\n      dispatch({ type: 'modal/show', payload: {component: IncomeModal, type: modalType, currentItem: currentItem }});\n      break;\n    case 3:\n      dispatch({ type: 'modal/show', payload: {component: TransferModal, type: modalType, currentItem: currentItem }});\n      break;\n    case 4:\n      dispatch({ type: 'modal/show', payload: {component: AdjustBalanceModal, type: modalType, currentItem: currentItem }});\n      break;\n  }\n}\n\nexport function imageHandler(record) {\n  const dispatch = getDvaApp()._store.dispatch;\n  dispatch({ type: 'modal/show', payload: {component: FlowImageUploadModal, type: 2, currentItem: record } });\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/hooks.js",
    "content": "import { useEffect, useState, useRef } from 'react';\nimport {useIntl, useSelector} from 'umi';\nimport { message } from 'antd';\n\nexport function useFocus() {\n  const htmlElRef = useRef(null);\n  const setFocus = () => {htmlElRef.current &&  htmlElRef.current.focus()};\n  return [ htmlElRef,  setFocus ];\n}\n\nexport function usePaginationAndData(queryResponse) {\n\n  const intl = useIntl();\n\n  const [dataAndPagination, setDataAndPagination] = useState({\n    data: [],\n    pagination: {\n      showQuickJumper: true,\n      current: 1,\n      pageSize: 10,\n      showTotal: (total, range) => intl.formatMessage({id:'pagination.showTotal'}, {range0:range[0], range1:range[1], total:total}),\n    }\n  });\n  useEffect(() => {\n    if (queryResponse && queryResponse.success) {\n      setDataAndPagination({\n        data: queryResponse.data.content,\n        pagination: {\n          ...dataAndPagination.pagination,\n          current: queryResponse.data.number+1,\n          pageSize: queryResponse.data.size,\n          total: queryResponse.data.totalElements\n        }\n      });\n    }\n  }, [queryResponse]);\n  return [dataAndPagination, setDataAndPagination]\n}\n\nexport function useResultPaginationAndData(queryResponse) {\n\n  const intl = useIntl();\n\n  const [dataAndPagination, setDataAndPagination] = useState({\n    data: [],\n    pagination: {\n      showQuickJumper: true,\n      current: 1,\n      pageSize: 10,\n      showTotal: (total, range) => intl.formatMessage({id:'pagination.showTotal'}, {range0:range[0], range1:range[1], total:total}),\n    }\n  });\n  useEffect(() => {\n    if (queryResponse && queryResponse.success) {\n      setDataAndPagination({\n        data: queryResponse.data.result.content,\n        pagination: {\n          ...dataAndPagination.pagination,\n          current: queryResponse.data.result.number+1,\n          pageSize: queryResponse.data.result.size,\n          total: queryResponse.data.result.totalElements\n        }\n      });\n    }\n  }, [queryResponse]);\n  return [dataAndPagination, setDataAndPagination]\n}\n\nexport function useResponseData(queryResponse) {\n  const [responseData, setResponseData] = useState([]);\n  useEffect(() => {\n    if (queryResponse && queryResponse.success) {\n      setResponseData(queryResponse.data);\n    }\n  }, [queryResponse]);\n  return [responseData, setResponseData];\n}\n\nexport function useResponseSelectData(queryResponse) {\n  const [responseSelectData, setResponseSelectData] = useState([]);\n  useEffect(() => {\n    if (queryResponse && queryResponse.success) {\n      setResponseSelectData(queryResponse.data.map(item => {\n        return {\n          value: item.id,\n          title: item.name,\n          label: item.name\n        }\n      }));\n    }\n  }, [queryResponse]);\n  return [responseSelectData, setResponseSelectData];\n}\n\nexport function useCurrencyResponseSelectData(queryResponse) {\n  const [responseSelectData, setResponseSelectData] = useState([]);\n  useEffect(() => {\n    if (queryResponse && queryResponse.success) {\n      setResponseSelectData(queryResponse.data.map(item => {\n        return {\n          value: item.code,\n          title: item.code,\n          label: item.code\n        }\n      }));\n    }\n  }, [queryResponse]);\n  return [responseSelectData, setResponseSelectData];\n}\n\nexport function useCategoryTreeSelectData(categoriesResponse) {\n  const [categories, setCategories] = useState([]);\n  useEffect(() => {\n    if (categoriesResponse && categoriesResponse.success) {\n      setCategories(categoriesResponse.data.map(item => {\n        return {\n          id: item.id,\n          pId: item.parentId,\n          value: item.id,\n          title: item.name\n        }\n      }));\n    }\n  }, [categoriesResponse]);\n  return [categories, setCategories];\n}\n\nexport function useImageEnable() {\n  const [enable, setEnable] = useState(false);\n  const { defaultBook } = useSelector(state => state.session);\n  useEffect(() => {\n    if (defaultBook && defaultBook.imageEnable) {\n      setEnable(true);\n    } else {\n      setEnable(false);\n    }\n  }, [defaultBook]);\n  return enable;\n}\n\nexport function useDescriptionEnable() {\n  const [enable, setEnable] = useState(false);\n  const { defaultBook } = useSelector(state => state.session);\n  useEffect(() => {\n    if (defaultBook && defaultBook.descriptionEnable) {\n      setEnable(true);\n    } else {\n      setEnable(false);\n    }\n  }, [defaultBook]);\n  return enable;\n}\n\nexport function useTimeFormat() {\n  const [format, setFormat] = useState('YYYY-MM-DD');\n  const { defaultBook } = useSelector(state => state.session);\n  useEffect(() => {\n    if (defaultBook && defaultBook.timeEnable) {\n      setFormat('YYYY-MM-DD HH:mm');\n    } else {\n      setFormat('YYYY-MM-DD');\n    }\n  }, [defaultBook]);\n  return format;\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/model.js",
    "content": "export const model = {\n  reducers: {\n    updateState(state, { payload }) {\n      return { ...state, ...payload }\n    },\n  },\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/request.js",
    "content": "import { extend } from \"umi-request\";\nimport { message } from 'antd';\n\nconst request = extend({\n  prefix: \"/api/v1/\",\n  // prefix: \"http://jz.jiukuaitech.com/api/v1/\",\n  timeout: 60000,\n  headers: {\n    \"Content-Type\": \"application/json\",\n  },\n  errorHandler: function(error) {\n    // error.response.status\n    if (error.data && error.data.errorMsg) {\n      message.error(error.data.errorMsg);\n    } else {\n      console.log(error);\n      message.error(\"系统异常，请稍后重试。\");\n    }\n  }\n});\n\n// 自动跳转到登陆页\nrequest.interceptors.response.use(async (response, options) => {\n  const data = await response.clone().json();\n  if (data.errorCode === 8 || data.errorCode === 10) {\n    window.location.href = '/signin';\n  }\n  return response;\n});\n\n// 全局处理异常\nrequest.interceptors.response.use(async (response, options) => {\n  if (response.status === 200) {\n    const data = await response.clone().json();\n    if (!data.success) {\n      message.error(data.errorMsg);\n    }\n  }\n  return response;\n});\n\nexport default request;\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/rules.js",
    "content": "import t from '@/utils/translate'\n\nexport const requiredRules = () => [\n  {\n    required: true,\n    message: t('rules.required')\n  }\n];\n\nexport const userNameRules = () => [\n  {\n    required: true,\n    message: t('rules.userName.required'),\n  },\n  {\n    pattern: \"^[A-Za-z0-9]{1,16}$\",\n    message: t('rules.userName.pattern'),\n  }\n];\n\nexport const passwordRules = () => [\n  {\n    required: true,\n    message: t('rules.password.required'),\n  },\n  {\n    pattern: \"^[A-Za-z0-9~`!@#\\\\$%\\\\^&\\\\*\\\\(\\\\)-_=\\\\+\\\\[\\\\]\\\\{\\\\}\\\\|]{6,32}$\",\n    message: t('rules.password.pattern'),\n  }\n];\n\nexport const emailRules = () => [\n  {\n    type: \"email\",\n    message: t('rules.email.pattern'),\n  }\n];\n\n// 账户名，类别名，标签名等。\nexport const nameRules = () => [\n  {\n    required: true,\n    message: t('rules.name.required'),\n  },\n  {\n    max: 16,\n    message: t('rules.name.pattern'),\n  }\n];\n\nexport const balanceRequiredRules = () => [\n  {\n    required: true,\n    message: t('rules.balance.required'),\n  },\n  {\n    pattern: \"^-?\\\\d{1,9}(\\\\.\\\\d{0,2})?$\",\n    message: t('rules.balance.pattern'),\n  }\n];\n\nexport const amountRequiredRules = () => [\n  {\n    required: true,\n    message: t('rules.amount.required'),\n  },\n  {\n    pattern: \"^-?\\\\d{1,9}(\\\\.\\\\d{0,2})?$\", //可以负数，代表退款\n    message: t('rules.amount.pattern'),\n  }\n];\n\nexport const amountRequiredRulesPositive = () => [\n  {\n    required: true,\n    message: t('rules.amount.required'),\n  },\n  {\n    pattern: \"^\\\\d{1,9}(\\\\.\\\\d{0,2})?$\",\n    message: t('rules.amount.pattern'),\n  }\n];\n\nexport const balanceRules = () => [\n  {\n    pattern: \"^-?\\\\d{1,9}(\\\\.\\\\d{0,2})?$\",\n    message: t('rules.balance.pattern'),\n  }\n];\n\nexport const descriptionRules = () => [\n  {\n    max: 16,\n    message: t('rules.description.pattern'),\n  }\n];\n\nexport const notesRules = () => [\n  {\n    max: 1024,\n    message: t('rules.notes.pattern'),\n  }\n];\n\nexport const timeRequiredRules = () => [\n  {\n    type: 'object',\n    required: true,\n    message: t('rules.time.required')\n  }\n];\n\nexport const timeRangeRequiredRules = () => [\n  {\n    type: 'array',\n    required: true,\n    message: t('rules.time.required')\n  }\n];\n\nexport const categoryRequiredRules = () => [\n  {\n    required: true,\n    message: t('rules.category.required')\n  }\n];\n\nexport const accountRequiredRules = () => [\n  {\n    required: true,\n    message: t('rules.account.required')\n  }\n];\n\nexport const limitRequiredRules = () => [\n  {\n    required: true,\n    message: t('rules.limit.required'),\n  },\n  {\n    pattern: \"^\\\\d{1,9}(\\\\.\\\\d{0,2})?$\",\n    message: t('rules.limit.pattern'),\n  }\n];\n\nexport const aprRules = () => [\n  {\n    pattern: \"^\\\\d{1,3}(\\\\.\\\\d{0,2})?$\",\n    message: t('rules.apr.pattern'),\n  }\n];\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/translate.js",
    "content": "import { useIntl, setLocale } from 'umi';\n\nexport default (id, args) => {\n\n  const intl = useIntl();\n\n  // setLocale('en-US', true);\n  // setLocale('zh-CN', true);\n\n  return intl.formatMessage({id:id}, {...args});\n\n}\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/util.js",
    "content": "import {history, useIntl, getDvaApp} from 'umi';\nimport moment from 'moment';\n\nexport function tableSortFormat(sorter) {\n  let orderDir = '';\n  if (sorter && sorter.field && sorter.order) {\n    orderDir = sorter.field;\n    if (sorter.order === 'ascend') {\n      orderDir += ',asc'\n    } else if (sorter.order === 'descend') {\n      orderDir += ',desc'\n    }\n  }\n  return orderDir;\n}\n\nexport function handleTableChange(pagination, _, sorter) {\n  const { query, pathname } = history.location;\n  const newQuery = Object.assign({}, query, {\n    page: pagination.current,\n    size: pagination.pageSize,\n    sort: tableSortFormat(sorter),\n  });\n  history.push({\n    pathname: pathname,\n    query: newQuery\n  });\n}\n\nexport function paginationChange(page, pageSize) {\n  const { query, pathname } = history.location;\n  const newQuery = Object.assign({}, query, {\n    page: page,\n    size: pageSize,\n  });\n  history.push({\n    pathname: pathname,\n    query: newQuery\n  });\n}\n\nexport function tableChangeQueryFormat(pagination, sorter) {\n  return {\n    page: pagination.current,\n    size: pagination.pageSize,\n    sort: tableSortFormat(sorter),\n  }\n}\n\nfunction offSpring(categories, category) {\n  return categories.reduce((r, item) => {\n    if (item.pId === category.value) {\n      r.push(item, ...offSpring(categories, item));\n    }\n    return r;\n  }, []);\n}\n\nfunction parseCreateTimeRange(values) {\n  if (values.createTimeRange && values.createTimeRange[0]) {\n    values.minTime = values.createTimeRange[0].valueOf();\n  }\n  if (values.createTimeRange && values.createTimeRange[1]) {\n    values.maxTime = values.createTimeRange[1].valueOf();\n  }\n  delete values.createTimeRange;\n}\n\nexport async function searchHandler(form) {\n  const values = await form.validateFields();\n  parseCreateTimeRange(values);\n  history.push({\n    pathname: location.pathname,\n    query: values\n  });\n}\n\nexport async function searchHandlerWithCategory(form) {\n  const values = await form.validateFields();\n  parseCreateTimeRange(values);\n  // if (values.categories) {\n  //   let offSpringCategories = values.categories.map(item => offSpring(categories, item));\n  //   offSpringCategories = offSpringCategories.flat();\n  //   values.categories = [...(values.categories.map(item => item.value)), ...(offSpringCategories.map(item => item.value))];\n  //   values.categories = [...new Set(values.categories)];\n  // }\n  history.push({\n    pathname: location.pathname,\n    query: values\n  });\n}\n\nexport function flowStatusToColor(code) {\n  switch (code) {\n    case 1:\n      return 'blue';\n    case 2:\n      return 'yellow';\n    case 3:\n      return 'red';\n    default:\n      break;\n  }\n}\n\nexport function getNull(obj) {\n  let newObj = { ...obj };\n  Object.keys(newObj).forEach(k => newObj[k] = null);\n  return newObj;\n}\n\nexport async function validateForm(form) {\n  try {\n    return await form.validateFields();\n  } catch (e) {\n    //console.log(e);\n  }\n}\n\n// 1-今天 2-本周 3-本月 4-今年 5-去年 6-7天内 7-30天内 8-1年内\nexport function radioValueToTimeRange(value) {\n  switch (value) {\n    case 1:\n      return [moment().startOf('day'), moment().endOf('day')];\n    case 2:\n      return [moment().startOf('week'), moment().endOf('week')]\n    case 3:\n      return [moment().startOf('month'), moment().endOf('month')];\n    case 4:\n      return [moment().startOf('year'), moment().endOf('year')];\n    case 5:\n      return [moment().subtract(1, 'years').startOf('year'), moment().subtract(1, 'years').endOf('year')];\n    case 6:\n      return [moment().subtract(7, 'days'), moment()];\n    case 7:\n      return [moment().subtract(30, 'days'), moment()];\n    case 8:\n      return [moment().subtract(1, 'years'), moment()];\n  }\n}\n\nexport function initPagination() {\n  const intl = useIntl();\n  return {\n    showQuickJumper: true,\n    current: 1,\n    pageSize: 15,\n    showTotal: (total, range) => intl.formatMessage({id:'pagination.showTotal'}, {range0:range[0], range1:range[1], total:total}),\n  }\n}\n\nexport function getPagination(data, pagination) {\n  return {\n    ...pagination,\n    current: data.number+1,\n    pageSize: data.size,\n    total: data.totalElements\n  };\n}\n\nexport function getTimeFormat() {\n  let timeFormat = 'YYYY-MM-DD';\n  if (getTimeEnable()) {\n    timeFormat = 'YYYY-MM-DD HH:mm';\n  }\n  return timeFormat;\n}\n\nexport function getTimeEnable() {\n  let defaultBook = getDvaApp()._store.getState().session.defaultBook;\n  if (defaultBook && defaultBook.timeEnable) return true;\n  else return false;\n}\n\nexport function getDescriptionEnable() {\n  let defaultBook = getDvaApp()._store.getState().session.defaultBook;\n  if (defaultBook && defaultBook.descriptionEnable) return true;\n  else return false;\n}\n\nexport function getImageEnable() {\n  let defaultBook = getDvaApp()._store.getState().session.defaultBook;\n  if (defaultBook && defaultBook.imageEnable) return true;\n  else return false;\n}\n\nexport function categoryTypeToCreateParam(type) {\n  switch (type) {\n    case 1:\n      return { expenseable: true };\n    case 2:\n      return { incomeable: true };\n    case 3:\n      return { transferable: true };\n  }\n}\n\nexport function refreshFlow(data) {\n  const dispatch = getDvaApp()._store.dispatch;\n  if (history.location.pathname === '/expenses') {\n    dispatch({ type: 'expenses/query', payload: history.location.query });\n  }\n  if (history.location.pathname === '/incomes') {\n    dispatch({ type: 'incomes/query', payload: history.location.query });\n  }\n  if (history.location.pathname === '/transfers') {\n    dispatch({ type: 'transfers/query', payload: history.location.query });\n  }\n  if (history.location.pathname === '/flows') {\n    dispatch({ type: 'flows/query', payload: history.location.query });\n  }\n  if (history.location.pathname === '/audit') {\n    dispatch({ type: 'audit/query', payload: history.location.query });\n  }\n  // if (history.location.pathname === '/accounts') {\n  //   dispatch({ type: 'accounts/refresh' });\n  // }\n  // if (history.location.pathname === '/checking-accounts') {\n  //   dispatch({ type: 'checkingAccounts/refresh', payload: data });\n  // }\n  // if (history.location.pathname === '/credit-accounts') {\n  //   dispatch({ type: 'creditAccounts/refresh', payload: data });\n  // }\n  // if (history.location.pathname === '/debt-accounts') {\n  //   dispatch({ type: 'debtAccounts/refresh', payload: data });\n  // }\n  // if (history.location.pathname === '/asset-accounts') {\n  //   dispatch({ type: 'assetAccounts/refresh', payload: data });\n  // }\n}\n\nexport function searchTreeArray(treeArray, value, key = 'id', reverse = true) {\n  for (var i = 0; i < treeArray.length; i++) {\n    const stack = [ treeArray[i] ];\n    while (stack.length) {\n      const node = stack[reverse ? 'pop' : 'shift']();\n      if (node[key] === value) return node;\n      node.children && stack.push(...node.children);\n    }\n  }\n  return null;\n}\n\n"
  },
  {
    "path": "bookkeeping-user-fe/src/utils/var.js",
    "content": "export const tableProp = {\n  size: 'small',\n  bordered: true,\n  rowKey: 'id',\n  pagination: false,\n  showSorterTooltip: false,\n  //sortDirections: ['asc', 'desc']\n}\n\nexport const spaceVProp = {\n  direction: 'vertical',\n  size: 'small',\n  style: { width:'100%' }\n}\n\nexport const inputNumberProp = {\n  style: { width:'100%' },\n  min: 0,\n  step: 1\n}\n\nexport const formProp = {\n  validateTrigger: ['onBlur']\n}\n\nexport const filterFormProp = {\n  size: 'small',\n  style: { padding:'10px', background: 'rgba(255,255,255,1)'}\n}\n\nexport const formItemLayout = {\n  labelCol: { span: 3 },\n  wrapperCol: { span: 21 },\n}\n\n"
  },
  {
    "path": "bookkeeping-user-fe/webpack.config.js",
    "content": "/**\n * 不是真实的 webpack 配置，仅为兼容 webstorm 和 intellij idea 代码跳转\n * ref: https://github.com/umijs/umi/issues/1109#issuecomment-423380125\n */\n\nmodule.exports = {\n  resolve: {\n    alias: {\n      '@': require('path').resolve(__dirname, 'src'),\n    },\n  },\n};\n"
  },
  {
    "path": "bookkeeping_user_flutter/.gitignore",
    "content": "# Miscellaneous\n*.class\n*.log\n*.pyc\n*.swp\n.DS_Store\n.atom/\n.buildlog/\n.history\n.svn/\n\n# IntelliJ related\n*.iml\n*.ipr\n*.iws\n.idea/\n\n# The .vscode folder contains launch configuration and tasks you configure in\n# VS Code which you may wish to be included in version control, so this line\n# is commented out by default.\n#.vscode/\n\n# Flutter/Dart/Pub related\n**/doc/api/\n**/ios/Flutter/.last_build_id\n.dart_tool/\n.flutter-plugins\n.flutter-plugins-dependencies\n.packages\n.pub-cache/\n.pub/\n/build/\n\n# Web related\nlib/generated_plugin_registrant.dart\n\n# Symbolication related\napp.*.symbols\n\n# Obfuscation related\napp.*.map.json\n\n# Android Studio will place build artifacts here\n/android/app/debug\n/android/app/profile\n/android/app/release\n"
  },
  {
    "path": "bookkeeping_user_flutter/.metadata",
    "content": "# This file tracks properties of this Flutter project.\n# Used by Flutter tool to assess capabilities and perform upgrades etc.\n#\n# This file should be version controlled.\n\nversion:\n  revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n  channel: stable\n\nproject_type: app\n\n# Tracks metadata for the flutter migrate command\nmigration:\n  platforms:\n    - platform: root\n      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n    - platform: linux\n      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n    - platform: macos\n      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n    - platform: windows\n      create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n      base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1\n\n  # User provided section\n\n  # List of Local paths (relative to this file) that should be\n  # ignored by the migrate tool.\n  #\n  # Files that are not part of the templates will be ignored by default.\n  unmanaged_files:\n    - 'lib/main.dart'\n    - 'ios/Runner.xcodeproj/project.pbxproj'\n"
  },
  {
    "path": "bookkeeping_user_flutter/README.md",
    "content": "# bookkeeping_user_flutter\n\nA new Flutter project.\n\n## Getting Started\n\nThis project is a starting point for a Flutter application.\n\nA few resources to get you started if this is your first Flutter project:\n\n- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)\n- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)\n\nFor help getting started with Flutter, view our\n[online documentation](https://flutter.dev/docs), which offers tutorials,\nsamples, guidance on mobile development, and a full API reference.\n"
  },
  {
    "path": "bookkeeping_user_flutter/analysis_options.yaml",
    "content": "# This file configures the analyzer, which statically analyzes Dart code to\n# check for errors, warnings, and lints.\n#\n# The issues identified by the analyzer are surfaced in the UI of Dart-enabled\n# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be\n# invoked from the command line by running `flutter analyze`.\n\n# The following line activates a set of recommended lints for Flutter apps,\n# packages, and plugins designed to encourage good coding practices.\ninclude: package:flutter_lints/flutter.yaml\n\nlinter:\n  # The lint rules applied to this project can be customized in the\n  # section below to disable rules from the `package:flutter_lints/flutter.yaml`\n  # included above or to enable additional rules. A list of all available lints\n  # and their documentation is published at\n  # https://dart-lang.github.io/linter/lints/index.html.\n  #\n  # Instead of disabling a lint rule for the entire project in the\n  # section below, it can also be suppressed for a single line of code\n  # or a specific dart file by using the `// ignore: name_of_lint` and\n  # `// ignore_for_file: name_of_lint` syntax on the line or in the file\n  # producing the lint.\n  rules:\n    # avoid_print: false  # Uncomment to disable the `avoid_print` rule\n    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule\n\n# Additional information about this file can be found at\n# https://dart.dev/guides/language/analysis-options\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/.gitignore",
    "content": "gradle-wrapper.jar\n/.gradle\n/captures/\n/gradlew\n/gradlew.bat\n/local.properties\nGeneratedPluginRegistrant.java\n\n# Remember to never publicly share your keystore.\n# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app\nkey.properties\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/build.gradle",
    "content": "def localProperties = new Properties()\ndef localPropertiesFile = rootProject.file('local.properties')\nif (localPropertiesFile.exists()) {\n    localPropertiesFile.withReader('UTF-8') { reader ->\n        localProperties.load(reader)\n    }\n}\n\ndef flutterRoot = localProperties.getProperty('flutter.sdk')\nif (flutterRoot == null) {\n    throw new GradleException(\"Flutter SDK not found. Define location with flutter.sdk in the local.properties file.\")\n}\n\ndef flutterVersionCode = localProperties.getProperty('flutter.versionCode')\nif (flutterVersionCode == null) {\n    flutterVersionCode = '1'\n}\n\ndef flutterVersionName = localProperties.getProperty('flutter.versionName')\nif (flutterVersionName == null) {\n    flutterVersionName = '1.0'\n}\n\napply plugin: 'com.android.application'\napply plugin: 'kotlin-android'\napply from: \"$flutterRoot/packages/flutter_tools/gradle/flutter.gradle\"\n\nandroid {\n    compileSdkVersion 32\n\n    sourceSets {\n        main.java.srcDirs += 'src/main/kotlin'\n    }\n\n    defaultConfig {\n        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).\n        applicationId \"com.jiukuaitech.bookkeeping_user_flutter\"\n        minSdkVersion 19\n        targetSdkVersion 30\n        versionCode flutterVersionCode.toInteger()\n        versionName flutterVersionName\n    }\n\n    buildTypes {\n        release {\n            // TODO: Add your own signing config for the release build.\n            // Signing with the debug keys for now, so `flutter run --release` works.\n            signingConfig signingConfigs.debug\n            lintOptions {\n                disable 'InvalidPackage'\n                disable \"Instantiatable\"\n                checkReleaseBuilds false\n                abortOnError false\n            }\n        }\n    }\n}\n\nflutter {\n    source '../..'\n}\n\ndependencies {\n    implementation \"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version\"\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/src/debug/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.jiukuaitech.bookkeeping_user_flutter\">\n    <!-- Flutter needs it to communicate with the running application\n         to allow setting breakpoints, to provide hot reload, etc.\n    -->\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.jiukuaitech.bookkeeping_user_flutter\">\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n   <application\n        android:label=\"九快记账\"\n        android:icon=\"@mipmap/ic_launcher\">\n        <activity\n            android:name=\".MainActivity\"\n            android:launchMode=\"singleTop\"\n            android:theme=\"@style/LaunchTheme\"\n            android:configChanges=\"orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode\"\n            android:hardwareAccelerated=\"true\"\n            android:windowSoftInputMode=\"adjustResize\">\n            <!-- Specifies an Android theme to apply to this Activity as soon as\n                 the Android process has started. This theme is visible to the user\n                 while the Flutter UI initializes. After that, this theme continues\n                 to determine the Window background behind the Flutter UI. -->\n            <meta-data\n              android:name=\"io.flutter.embedding.android.NormalTheme\"\n              android:resource=\"@style/NormalTheme\"\n              />\n            <!-- Displays an Android View that continues showing the launch screen\n                 Drawable until Flutter paints its first frame, then this splash\n                 screen fades out. A splash screen is useful to avoid any visual\n                 gap between the end of Android's launch screen and the painting of\n                 Flutter's first frame. -->\n            <meta-data\n              android:name=\"io.flutter.embedding.android.SplashScreenDrawable\"\n              android:resource=\"@drawable/launch_background\"\n              />\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <!-- Don't delete the meta-data below.\n             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->\n        <meta-data\n            android:name=\"flutterEmbedding\"\n            android:value=\"2\" />\n    </application>\n</manifest>\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/src/main/kotlin/com/whlcsj/bookkeeping_user_flutter/MainActivity.kt",
    "content": "package com.jiukuaitech.bookkeeping_user_flutter\n\nimport io.flutter.embedding.android.FlutterActivity\n\nclass MainActivity: FlutterActivity() {\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/src/main/res/drawable/launch_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@android:color/white\" />\n\n    <!-- You can insert your own image assets here -->\n    <!-- <item>\n        <bitmap\n            android:gravity=\"center\"\n            android:src=\"@mipmap/launch_image\" />\n    </item> -->\n</layer-list>\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/src/main/res/drawable-v21/launch_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Modify this file to customize your launch splash screen -->\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"?android:colorBackground\" />\n\n    <!-- You can insert your own image assets here -->\n    <!-- <item>\n        <bitmap\n            android:gravity=\"center\"\n            android:src=\"@mipmap/launch_image\" />\n    </item> -->\n</layer-list>\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/src/main/res/values/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->\n    <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Light.NoTitleBar\">\n        <!-- Show a splash screen on the activity. Automatically removed when\n             Flutter draws its first frame -->\n        <item name=\"android:windowBackground\">@drawable/launch_background</item>\n    </style>\n    <!-- Theme applied to the Android Window as soon as the process has started.\n         This theme determines the color of the Android Window while your\n         Flutter UI initializes, as well as behind your Flutter UI while its\n         running.\n         \n         This Theme is only used starting with V2 of Flutter's Android embedding. -->\n    <style name=\"NormalTheme\" parent=\"@android:style/Theme.Light.NoTitleBar\">\n        <item name=\"android:windowBackground\">?android:colorBackground</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/src/main/res/values-night/styles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->\n    <style name=\"LaunchTheme\" parent=\"@android:style/Theme.Black.NoTitleBar\">\n        <!-- Show a splash screen on the activity. Automatically removed when\n             Flutter draws its first frame -->\n        <item name=\"android:windowBackground\">@drawable/launch_background</item>\n    </style>\n    <!-- Theme applied to the Android Window as soon as the process has started.\n         This theme determines the color of the Android Window while your\n         Flutter UI initializes, as well as behind your Flutter UI while its\n         running.\n         \n         This Theme is only used starting with V2 of Flutter's Android embedding. -->\n    <style name=\"NormalTheme\" parent=\"@android:style/Theme.Black.NoTitleBar\">\n        <item name=\"android:windowBackground\">?android:colorBackground</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/app/src/profile/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.jiukuaitech.bookkeeping_user_flutter\">\n    <!-- Flutter needs it to communicate with the running application\n         to allow setting breakpoints, to provide hot reload, etc.\n    -->\n    <uses-permission android:name=\"android.permission.INTERNET\"/>\n</manifest>\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/build.gradle",
    "content": "buildscript {\n    ext.kotlin_version = '1.6.21'\n    repositories {\n        google()\n        jcenter()\n    }\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.1.0'\n        classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version\"\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n\nrootProject.buildDir = '../build'\nsubprojects {\n    project.buildDir = \"${rootProject.buildDir}/${project.name}\"\n    project.evaluationDependsOn(':app')\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Jun 23 08:50:38 CEST 2017\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.7-all.zip\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx1536M\nandroid.useAndroidX=true\nandroid.enableJetifier=true\n"
  },
  {
    "path": "bookkeeping_user_flutter/android/settings.gradle",
    "content": "include ':app'\n\ndef localPropertiesFile = new File(rootProject.projectDir, \"local.properties\")\ndef properties = new Properties()\n\nassert localPropertiesFile.exists()\nlocalPropertiesFile.withReader(\"UTF-8\") { reader -> properties.load(reader) }\n\ndef flutterSdkPath = properties.getProperty(\"flutter.sdk\")\nassert flutterSdkPath != null, \"flutter.sdk not set in local.properties\"\napply from: \"$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle\"\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/.gitignore",
    "content": "*.mode1v3\n*.mode2v3\n*.moved-aside\n*.pbxuser\n*.perspectivev3\n**/*sync/\n.sconsign.dblite\n.tags*\n**/.vagrant/\n**/DerivedData/\nIcon?\n**/Pods/\n**/.symlinks/\nprofile\nxcuserdata\n**/.generated/\nFlutter/App.framework\nFlutter/Flutter.framework\nFlutter/Flutter.podspec\nFlutter/Generated.xcconfig\nFlutter/ephemeral/\nFlutter/app.flx\nFlutter/app.zip\nFlutter/flutter_assets/\nFlutter/flutter_export_environment.sh\nServiceDefinitions.json\nRunner/GeneratedPluginRegistrant.*\n\n# Exceptions to above rules.\n!default.mode1v3\n!default.mode2v3\n!default.pbxuser\n!default.perspectivev3\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Flutter/AppFrameworkInfo.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>CFBundleDevelopmentRegion</key>\n  <string>en</string>\n  <key>CFBundleExecutable</key>\n  <string>App</string>\n  <key>CFBundleIdentifier</key>\n  <string>io.flutter.flutter.app</string>\n  <key>CFBundleInfoDictionaryVersion</key>\n  <string>6.0</string>\n  <key>CFBundleName</key>\n  <string>App</string>\n  <key>CFBundlePackageType</key>\n  <string>FMWK</string>\n  <key>CFBundleShortVersionString</key>\n  <string>1.0</string>\n  <key>CFBundleSignature</key>\n  <string>????</string>\n  <key>CFBundleVersion</key>\n  <string>1.0</string>\n  <key>MinimumOSVersion</key>\n  <string>11.0</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Flutter/Debug.xcconfig",
    "content": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Flutter/Release.xcconfig",
    "content": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"Generated.xcconfig\"\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Podfile",
    "content": "# Uncomment this line to define a global platform for your project\n# platform :ios, '11.0'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_ios_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n  use_modular_headers!\n\n  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_ios_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner/AppDelegate.swift",
    "content": "import UIKit\nimport Flutter\n\n@UIApplicationMain\n@objc class AppDelegate: FlutterAppDelegate {\n  override func application(\n    _ application: UIApplication,\n    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n  ) -> Bool {\n    GeneratedPluginRegistrant.register(with: self)\n    return super.application(application, didFinishLaunchingWithOptions: launchOptions)\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-29x29@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-40x40@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-60x60@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"60x60\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-60x60@3x.png\",\n      \"scale\" : \"3x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-20x20@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"29x29\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-40x40@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"40x40\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-76x76@1x.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"76x76\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-76x76@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"83.5x83.5\",\n      \"idiom\" : \"ipad\",\n      \"filename\" : \"Icon-App-83.5x83.5@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"1024x1024\",\n      \"idiom\" : \"ios-marketing\",\n      \"filename\" : \"Icon-App-1024x1024@1x.png\",\n      \"scale\" : \"1x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage@2x.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"filename\" : \"LaunchImage@3x.png\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md",
    "content": "# Launch Screen Assets\n\nYou can customize the launch screen with your own desired assets by replacing the image files in this directory.\n\nYou can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images."
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner/Base.lproj/LaunchScreen.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"12121\" systemVersion=\"16G29\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" launchScreen=\"YES\" colorMatched=\"YES\" initialViewController=\"01J-lp-oVM\">\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"12089\"/>\n    </dependencies>\n    <scenes>\n        <!--View Controller-->\n        <scene sceneID=\"EHf-IW-A2E\">\n            <objects>\n                <viewController id=\"01J-lp-oVM\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"Ydg-fD-yQy\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"xbc-2k-c8Z\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"Ze5-6b-2t3\">\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <subviews>\n                            <imageView opaque=\"NO\" clipsSubviews=\"YES\" multipleTouchEnabled=\"YES\" contentMode=\"center\" image=\"LaunchImage\" translatesAutoresizingMaskIntoConstraints=\"NO\" id=\"YRO-k0-Ey4\">\n                            </imageView>\n                        </subviews>\n                        <color key=\"backgroundColor\" red=\"1\" green=\"1\" blue=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"sRGB\"/>\n                        <constraints>\n                            <constraint firstItem=\"YRO-k0-Ey4\" firstAttribute=\"centerX\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerX\" id=\"1a2-6s-vTC\"/>\n                            <constraint firstItem=\"YRO-k0-Ey4\" firstAttribute=\"centerY\" secondItem=\"Ze5-6b-2t3\" secondAttribute=\"centerY\" id=\"4X2-HB-R7a\"/>\n                        </constraints>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"iYj-Kq-Ea1\" userLabel=\"First Responder\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n            <point key=\"canvasLocation\" x=\"53\" y=\"375\"/>\n        </scene>\n    </scenes>\n    <resources>\n        <image name=\"LaunchImage\" width=\"168\" height=\"185\"/>\n    </resources>\n</document>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner/Base.lproj/Main.storyboard",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3.0\" toolsVersion=\"10117\" systemVersion=\"15F34\" targetRuntime=\"iOS.CocoaTouch\" propertyAccessControl=\"none\" useAutolayout=\"YES\" useTraitCollections=\"YES\" initialViewController=\"BYZ-38-t0r\">\n    <dependencies>\n        <deployment identifier=\"iOS\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.IBCocoaTouchPlugin\" version=\"10085\"/>\n    </dependencies>\n    <scenes>\n        <!--Flutter View Controller-->\n        <scene sceneID=\"tne-QT-ifu\">\n            <objects>\n                <viewController id=\"BYZ-38-t0r\" customClass=\"FlutterViewController\" sceneMemberID=\"viewController\">\n                    <layoutGuides>\n                        <viewControllerLayoutGuide type=\"top\" id=\"y3c-jy-aDJ\"/>\n                        <viewControllerLayoutGuide type=\"bottom\" id=\"wfy-db-euE\"/>\n                    </layoutGuides>\n                    <view key=\"view\" contentMode=\"scaleToFill\" id=\"8bC-Xf-vdC\">\n                        <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"600\" height=\"600\"/>\n                        <autoresizingMask key=\"autoresizingMask\" widthSizable=\"YES\" heightSizable=\"YES\"/>\n                        <color key=\"backgroundColor\" white=\"1\" alpha=\"1\" colorSpace=\"custom\" customColorSpace=\"calibratedWhite\"/>\n                    </view>\n                </viewController>\n                <placeholder placeholderIdentifier=\"IBFirstResponder\" id=\"dkx-z0-nzr\" sceneMemberID=\"firstResponder\"/>\n            </objects>\n        </scene>\n    </scenes>\n</document>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleDisplayName</key>\n\t<string>九快记账</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>bookkeeping_user_flutter</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(FLUTTER_BUILD_NAME)</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(FLUTTER_BUILD_NUMBER)</string>\n\t<key>LSRequiresIPhoneOS</key>\n\t<true/>\n\t<key>UILaunchStoryboardName</key>\n\t<string>LaunchScreen</string>\n\t<key>UIMainStoryboardFile</key>\n\t<string>Main</string>\n\t<key>UISupportedInterfaceOrientations</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UISupportedInterfaceOrientations~ipad</key>\n\t<array>\n\t\t<string>UIInterfaceOrientationPortrait</string>\n\t\t<string>UIInterfaceOrientationPortraitUpsideDown</string>\n\t\t<string>UIInterfaceOrientationLandscapeLeft</string>\n\t\t<string>UIInterfaceOrientationLandscapeRight</string>\n\t</array>\n\t<key>UIViewControllerBasedStatusBarAppearance</key>\n\t<false/>\n\t<key>CADisableMinimumFrameDurationOnPhone</key>\n\t<true/>\n\t<key>UIApplicationSupportsIndirectInputEvents</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner/Runner-Bridging-Header.h",
    "content": "#import \"GeneratedPluginRegistrant.h\"\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 54;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };\n\t\t317A2C53BB5FF676168ECE72 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DA02CB907CE0F6FF8FB20B4 /* Pods_Runner.framework */; };\n\t\t3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };\n\t\t74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };\n\t\t97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };\n\t\t97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };\n\t\t97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t9705A1C41CF9048500538489 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = \"<group>\"; };\n\t\t1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = \"<group>\"; };\n\t\t3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = \"<group>\"; };\n\t\t74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = \"Runner-Bridging-Header.h\"; sourceTree = \"<group>\"; };\n\t\t74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = \"<group>\"; };\n\t\t96F37FC13C924C3FF0E5F18A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.release.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = \"<group>\"; };\n\t\t9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = \"<group>\"; };\n\t\t97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = \"<group>\"; };\n\t\t97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = \"<group>\"; };\n\t\t97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t9DA02CB907CE0F6FF8FB20B4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t9DCD06E0BA9999D42EED641A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.profile.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tDCA4C3B4092AC92C0938E696 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.debug.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t97C146EB1CF9000F007C117D /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t317A2C53BB5FF676168ECE72 /* Pods_Runner.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t9740EEB11CF90186004384FC /* Flutter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,\n\t\t\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */,\n\t\t\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */,\n\t\t\t\t9740EEB31CF90195004384FC /* Generated.xcconfig */,\n\t\t\t);\n\t\t\tname = Flutter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146E51CF9000F007C117D = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t9740EEB11CF90186004384FC /* Flutter */,\n\t\t\t\t97C146F01CF9000F007C117D /* Runner */,\n\t\t\t\t97C146EF1CF9000F007C117D /* Products */,\n\t\t\t\tDA0E3430B2CF1B346EC449CC /* Pods */,\n\t\t\t\tB1414E7CCCC57CC8C5D302AD /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146EF1CF9000F007C117D /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146EE1CF9000F007C117D /* Runner.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146F01CF9000F007C117D /* Runner */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146FA1CF9000F007C117D /* Main.storyboard */,\n\t\t\t\t97C146FD1CF9000F007C117D /* Assets.xcassets */,\n\t\t\t\t97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,\n\t\t\t\t97C147021CF9000F007C117D /* Info.plist */,\n\t\t\t\t1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,\n\t\t\t\t1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,\n\t\t\t\t74858FAE1ED2DC5600515810 /* AppDelegate.swift */,\n\t\t\t\t74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,\n\t\t\t);\n\t\t\tpath = Runner;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB1414E7CCCC57CC8C5D302AD /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t9DA02CB907CE0F6FF8FB20B4 /* Pods_Runner.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDA0E3430B2CF1B346EC449CC /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tDCA4C3B4092AC92C0938E696 /* Pods-Runner.debug.xcconfig */,\n\t\t\t\t96F37FC13C924C3FF0E5F18A /* Pods-Runner.release.xcconfig */,\n\t\t\t\t9DCD06E0BA9999D42EED641A /* Pods-Runner.profile.xcconfig */,\n\t\t\t);\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t97C146ED1CF9000F007C117D /* Runner */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget \"Runner\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t330760EC9F725B8A7FB98CB4 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t9740EEB61CF901F6004384FC /* Run Script */,\n\t\t\t\t97C146EA1CF9000F007C117D /* Sources */,\n\t\t\t\t97C146EB1CF9000F007C117D /* Frameworks */,\n\t\t\t\t97C146EC1CF9000F007C117D /* Resources */,\n\t\t\t\t9705A1C41CF9048500538489 /* Embed Frameworks */,\n\t\t\t\t3B06AD1E1E4923F5004D2608 /* Thin Binary */,\n\t\t\t\t4E2C97FAD6B63ABA3AB947A8 /* [CP] Embed Pods Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = Runner;\n\t\t\tproductName = Runner;\n\t\t\tproductReference = 97C146EE1CF9000F007C117D /* Runner.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t97C146E61CF9000F007C117D /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastUpgradeCheck = 1300;\n\t\t\t\tORGANIZATIONNAME = \"\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t97C146ED1CF9000F007C117D = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 7.3.1;\n\t\t\t\t\t\tLastSwiftMigration = 1100;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject \"Runner\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 97C146E51CF9000F007C117D;\n\t\t\tproductRefGroup = 97C146EF1CF9000F007C117D /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t97C146ED1CF9000F007C117D /* Runner */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t97C146EC1CF9000F007C117D /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,\n\t\t\t\t3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,\n\t\t\t\t97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,\n\t\t\t\t97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t330760EC9F725B8A7FB98CB4 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Thin Binary\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"/bin/sh \\\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\\\" embed_and_thin\";\n\t\t};\n\t\t4E2C97FAD6B63ABA3AB947A8 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t9740EEB61CF901F6004384FC /* Run Script */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\talwaysOutOfDate = 1;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\tname = \"Run Script\";\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"/bin/sh \\\"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\\\" build\";\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t97C146EA1CF9000F007C117D /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,\n\t\t\t\t1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXVariantGroup section */\n\t\t97C146FA1CF9000F007C117D /* Main.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t97C146FB1CF9000F007C117D /* Base */,\n\t\t\t);\n\t\t\tname = Main.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t97C147001CF9000F007C117D /* Base */,\n\t\t\t);\n\t\t\tname = LaunchScreen.storyboard;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t249021D3217E4FDB00AE95B9 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 11.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t249021D4217E4FDB00AE95B9 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tDEVELOPMENT_TEAM = PB6FXZT6M2;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.jiukuaitech.bookkeepingUserFlutter;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t97C147031CF9000F007C117D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 11.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t97C147041CF9000F007C117D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 11.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t97C147061CF9000F007C117D /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tDEVELOPMENT_TEAM = PB6FXZT6M2;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.jiukuaitech.bookkeepingUserFlutter;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t97C147071CF9000F007C117D /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = \"$(FLUTTER_BUILD_NUMBER)\";\n\t\t\t\tDEVELOPMENT_TEAM = PB6FXZT6M2;\n\t\t\t\tENABLE_BITCODE = NO;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.jiukuaitech.bookkeepingUserFlutter;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"Runner/Runner-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t97C146E91CF9000F007C117D /* Build configuration list for PBXProject \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t97C147031CF9000F007C117D /* Debug */,\n\t\t\t\t97C147041CF9000F007C117D /* Release */,\n\t\t\t\t249021D3217E4FDB00AE95B9 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t97C147061CF9000F007C117D /* Debug */,\n\t\t\t\t97C147071CF9000F007C117D /* Release */,\n\t\t\t\t249021D4217E4FDB00AE95B9 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 97C146E61CF9000F007C117D /* Project object */;\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PreviewsEnabled</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1300\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n               BuildableName = \"Runner.app\"\n               BlueprintName = \"Runner\"\n               ReferencedContainer = \"container:Runner.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <Testables>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Profile\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"Animation Hitches\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <PathRunnable\n         runnableDebuggingMode = \"0\"\n         FilePath = \"/System/Applications/Contacts.app\">\n      </PathRunnable>\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"97C146ED1CF9000F007C117D\"\n            BuildableName = \"Runner.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>PreviewsEnabled</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/accounts.dart",
    "content": "export 'bloc/account_expenseable/account_expenseable_bloc.dart';\nexport 'bloc/account_incomeable/account_incomeable_bloc.dart';\nexport 'bloc/account_transfer_from_able/account_transfer_from_able_bloc.dart';\nexport 'bloc/account_transfer_to_able/account_transfer_to_able_bloc.dart';\nexport 'bloc/account_enable/account_enable_bloc.dart';\nexport 'bloc/accounts/accounts_bloc.dart';\nexport 'bloc/account_fetch/account_fetch_bloc.dart';\nexport 'bloc/account_adjust_balance/account_adjust_balance_bloc.dart';\nexport 'bloc/account_form/account_form_bloc.dart';\nexport 'data/account_repository.dart';\nexport 'data/models/account.dart';\nexport 'data/models/account_query_request.dart';\nexport 'data/models/account_form_request.dart';\nexport 'data/models/adjust_balance_request.dart';\nexport 'ui/account_detail_page.dart';\nexport 'ui/account_form_page.dart';\nexport 'ui/checking_account_form_page.dart';\nexport 'ui/credit_account_form_page.dart';\nexport 'ui/debt_account_form_page.dart';\nexport 'ui/asset_account_form_page.dart';\nexport 'ui/account_adjust_balance.dart';\nexport 'ui/widgets/order_button.dart';\nexport 'ui/accounts_page.dart';\nexport 'ui/widgets/adjust_balance/description_input.dart';\nexport 'ui/widgets/adjust_balance/date_time_input.dart';\nexport 'ui/widgets/adjust_balance/balance_input.dart';\nexport 'ui/widgets/adjust_balance/notes_input.dart';\nexport 'ui/widgets/adjust_balance/adjust_balance_form.dart';\nexport 'ui/widgets/account_form/account_balance_input.dart';\nexport 'ui/widgets/account_form/name_input.dart';\nexport 'ui/widgets/account_form/currency_input.dart';\nexport 'ui/widgets/account_form/account_limit_input.dart';\nexport 'ui/widgets/account_form/account_billday_input.dart';\nexport 'ui/widgets/account_form/account_apr_input.dart';\nexport 'ui/widgets/account_form/no_input.dart';\nexport 'ui/widgets/account_form/expenseable_input.dart';\nexport 'ui/widgets/account_form/incomeable_input.dart';\nexport 'ui/widgets/account_form/transfer_from_able_input.dart';\nexport 'ui/widgets/account_form/transfer_to_able_input.dart';\nexport 'ui/widgets/account_form/inclue_input.dart';\nexport 'ui/widgets/account_form/notes_input.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_adjust_balance/account_adjust_balance_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:flutter/material.dart';\nimport 'package:formz/formz.dart';\nimport 'package:meta/meta.dart';\n\nimport '/accounts/accounts.dart';\nimport '/flows/flows.dart';\n\npart 'account_adjust_balance_event.dart';\npart 'account_adjust_balance_state.dart';\n\nclass AccountAdjustBalanceBloc extends Bloc<AccountAdjustBalanceEvent, AccountAdjustBalanceState> {\n\n  final AccountRepository accountRepository;\n\n  AccountAdjustBalanceBloc({\n    required this.accountRepository,\n  }) : super(AccountAdjustBalanceState()) {\n    on<AccountAdjustBalanceDescriptionChanged>(_onDescriptionChanged);\n    on<AccountAdjustBalanceNotesChanged>(_onNotesChanged);\n    on<AccountAdjustBalanceCreateDateChanged>(_onCreateDateChanged);\n    on<AccountAdjustBalanceCreateTimeChanged>(_onCreateTimeChanged);\n    on<AccountAdjustBalanceBalanceChanged>(_onBalanceChanged);\n    on<AccountAdjustBalanceDefaultLoaded>(_onDefaultLoaded);\n    on<AccountAdjustBalanceSubmitted>(_onSubmitted);\n  }\n\n  void _onDescriptionChanged(AccountAdjustBalanceDescriptionChanged event, Emitter<AccountAdjustBalanceState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(description: event.description),\n    ));\n  }\n\n  void _onNotesChanged(AccountAdjustBalanceNotesChanged event, Emitter<AccountAdjustBalanceState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(notes: event.notes),\n    ));\n  }\n\n  void _onCreateDateChanged(AccountAdjustBalanceCreateDateChanged event, Emitter<AccountAdjustBalanceState> emit) {\n    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(state.request.createTime ?? DateTime.now().millisecondsSinceEpoch);\n    dateTime = DateTime(event.dateTime.year, event.dateTime.month, event.dateTime.day, dateTime.hour, dateTime.minute);\n    emit(state.copyWith(\n      request: state.request.copyWith(createTime: dateTime.millisecondsSinceEpoch),\n    ));\n  }\n\n  void _onCreateTimeChanged(AccountAdjustBalanceCreateTimeChanged event, Emitter<AccountAdjustBalanceState> emit) {\n    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(state.request.createTime ?? DateTime.now().millisecondsSinceEpoch);\n    dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, event.time.hour, event.time.minute);\n    emit(state.copyWith(\n      request: state.request.copyWith(createTime: dateTime.millisecondsSinceEpoch),\n    ));\n  }\n\n  void _onDefaultLoaded(AccountAdjustBalanceDefaultLoaded event, Emitter<AccountAdjustBalanceState> emit) {\n    emit(state.copyWith(\n        status: FormzStatus.pure,\n        request: state.request.copyWith(\n          description: event.adjustBalance?.description ?? '',\n          createTime: event.type != 2 ? DateTime.now().millisecondsSinceEpoch : event.adjustBalance!.createTime,\n          notes: event.adjustBalance?.notes ?? '',\n        )\n    ));\n  }\n\n  void _onBalanceChanged(AccountAdjustBalanceBalanceChanged event, Emitter<AccountAdjustBalanceState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(balance: event.balance),\n    ));\n  }\n\n  void _onSubmitted(AccountAdjustBalanceSubmitted event, Emitter<AccountAdjustBalanceState> emit) async {\n    try {\n      bool result = false;\n      switch (event.type) {\n        case 1:\n          result = await accountRepository.adjustBalance(event.accountId!, state.request);\n          break;\n        case 2:\n          result = await accountRepository.updateAdjustBalance(event.adjustBalance!.id, state.request);\n          break;\n        default:\n          break;\n      }\n      if (result) {\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n      } else {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    } catch (_) {\n      emit(state.copyWith(status: FormzStatus.submissionFailure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_adjust_balance/account_adjust_balance_event.dart",
    "content": "part of 'account_adjust_balance_bloc.dart';\n\n@immutable\nabstract class AccountAdjustBalanceEvent extends Equatable {\n  const AccountAdjustBalanceEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass AccountAdjustBalanceCreateTimeChanged extends AccountAdjustBalanceEvent {\n  const AccountAdjustBalanceCreateTimeChanged(this.time);\n  final TimeOfDay time;\n  @override\n  List<Object> get props => [time];\n}\n\nclass AccountAdjustBalanceCreateDateChanged extends AccountAdjustBalanceEvent {\n  const AccountAdjustBalanceCreateDateChanged(this.dateTime);\n  final DateTime dateTime;\n  @override\n  List<Object> get props => [dateTime];\n}\n\nclass AccountAdjustBalanceBalanceChanged extends AccountAdjustBalanceEvent {\n  const AccountAdjustBalanceBalanceChanged(this.balance);\n  final String balance;\n  @override\n  List<Object> get props => [balance];\n}\n\n\nclass AccountAdjustBalanceDescriptionChanged extends AccountAdjustBalanceEvent {\n  const AccountAdjustBalanceDescriptionChanged(this.description);\n  final String description;\n  @override\n  List<Object> get props => [description];\n}\n\nclass AccountAdjustBalanceNotesChanged extends AccountAdjustBalanceEvent {\n  const AccountAdjustBalanceNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass AccountAdjustBalanceDefaultLoaded extends AccountAdjustBalanceEvent {\n  final int type;\n  final AdjustBalance? adjustBalance;\n  const AccountAdjustBalanceDefaultLoaded(this.type, this.adjustBalance);\n  @override\n  List<Object?> get props => [type, adjustBalance];\n}\n\nclass AccountAdjustBalanceSubmitted extends AccountAdjustBalanceEvent {\n  final int? accountId;\n  final int type;\n  final AdjustBalance? adjustBalance;\n  const AccountAdjustBalanceSubmitted(this.type, this.accountId, this.adjustBalance);\n  @override\n  List<Object?> get props => [type, accountId, adjustBalance];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_adjust_balance/account_adjust_balance_state.dart",
    "content": "part of 'account_adjust_balance_bloc.dart';\n\n@immutable\nclass AccountAdjustBalanceState extends Equatable {\n\n  final FormzStatus status;\n  final AdjustBalanceRequest request;\n\n  const AccountAdjustBalanceState({\n    this.status = FormzStatus.pure,\n    this.request = const AdjustBalanceRequest(),\n  });\n\n  AccountAdjustBalanceState copyWith({\n    FormzStatus? status,\n    AdjustBalanceRequest? request,\n  }) {\n    return AccountAdjustBalanceState(\n      status: status ?? this.status,\n      request: request ?? this.request,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, request];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_enable/account_enable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\n\npart 'account_enable_event.dart';\npart 'account_enable_state.dart';\n\nclass AccountEnableBloc extends Bloc<AccountEnableEvent, AccountEnableState> {\n\n  final AccountRepository accountRepository;\n\n  AccountEnableBloc({\n    required this.accountRepository\n  }) : super(AccountEnableStateLoadInProgress()) {\n    on<AccountEnableLoaded>(_onAccountEnableLoaded);\n  }\n\n  void _onAccountEnableLoaded(_, Emitter<AccountEnableState> emit) async {\n    // 只加载一次数据\n    if (state is AccountEnableStateLoadSuccess) return;\n      emit(AccountEnableStateLoadInProgress());\n      final accounts = await accountRepository.getEnable();\n      emit(AccountEnableStateLoadSuccess(accounts));\n\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_enable/account_enable_event.dart",
    "content": "part of 'account_enable_bloc.dart';\n\nabstract class AccountEnableEvent extends Equatable {\n  const AccountEnableEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountEnableLoaded extends AccountEnableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_enable/account_enable_state.dart",
    "content": "part of 'account_enable_bloc.dart';\n\nabstract class AccountEnableState extends Equatable {\n  const AccountEnableState();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountEnableStateLoadInProgress extends AccountEnableState { }\n\nclass AccountEnableStateLoadSuccess extends AccountEnableState {\n  final List<Account> accounts;\n  const AccountEnableStateLoadSuccess(this.accounts);\n  @override\n  List<Object> get props => [accounts];\n}\n\nclass AccountEnableStateLoadFailure extends AccountEnableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_expenseable/account_expenseable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\n\npart 'account_expenseable_event.dart';\npart 'account_expenseable_state.dart';\n\nclass AccountExpenseableBloc extends Bloc<AccountExpenseableEvent, AccountExpenseableState> {\n\n  final AccountRepository accountRepository;\n\n  AccountExpenseableBloc({\n    required this.accountRepository\n  }) : super(AccountExpenseableStateLoadInProgress()) {\n    on<AccountExpenseableLoaded>(_onAccountExpenseableLoaded);\n  }\n\n  void _onAccountExpenseableLoaded(_, Emitter<AccountExpenseableState> emit) async {\n    // 只加载一次数据\n    if (state is AccountExpenseableStateLoadSuccess) return;\n    try {\n      emit(AccountExpenseableStateLoadInProgress());\n      final accounts = await accountRepository.getExpenseable();\n      emit(AccountExpenseableStateLoadSuccess(accounts));\n    } catch (_) {\n      print(_);\n      emit(AccountExpenseableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_expenseable/account_expenseable_event.dart",
    "content": "part of 'account_expenseable_bloc.dart';\n\nabstract class AccountExpenseableEvent extends Equatable {\n  const AccountExpenseableEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountExpenseableLoaded extends AccountExpenseableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_expenseable/account_expenseable_state.dart",
    "content": "part of 'account_expenseable_bloc.dart';\n\nabstract class AccountExpenseableState extends Equatable {\n  const AccountExpenseableState();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountExpenseableStateLoadInProgress extends AccountExpenseableState { }\n\nclass AccountExpenseableStateLoadSuccess extends AccountExpenseableState {\n  final List<Account> accounts;\n  const AccountExpenseableStateLoadSuccess(this.accounts);\n  @override\n  List<Object> get props => [accounts];\n}\n\nclass AccountExpenseableStateLoadFailure extends AccountExpenseableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_fetch/account_fetch_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\n\nimport '/accounts/accounts.dart';\nimport '/commons/commons.dart';\n\npart 'account_fetch_event.dart';\npart 'account_fetch_state.dart';\n\nclass AccountFetchBloc extends Bloc<AccountFetchEvent, AccountFetchState> {\n\n  final AccountRepository accountRepository;\n\n  AccountFetchBloc({\n    required this.accountRepository,\n  }) : super(AccountFetchState()) {\n    on<AccountFetched>(_onFetched);\n    on<AccountLoadDefault>(_onDefault);\n  }\n\n  void _onDefault(AccountLoadDefault event, Emitter<AccountFetchState> emit) {\n    emit(state.copyWith(\n      status: LoadDataStatus.success,\n      account: event.account\n    ));\n  }\n\n  void _onFetched(_, Emitter<AccountFetchState> emit) async {\n    try {\n      emit(state.copyWith(status: LoadDataStatus.progress));\n      final account = await accountRepository.get(state.account!.id);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        account: account,\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_fetch/account_fetch_event.dart",
    "content": "part of 'account_fetch_bloc.dart';\n\n@immutable\nclass AccountFetchEvent extends Equatable {\n  const AccountFetchEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountFetched extends AccountFetchEvent {}\n\nclass AccountLoadDefault extends AccountFetchEvent {\n  final Account account;\n  const AccountLoadDefault({\n    required this.account,\n  });\n  List<Object> get props => [account];\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_fetch/account_fetch_state.dart",
    "content": "part of 'account_fetch_bloc.dart';\n\n@immutable\nclass AccountFetchState extends Equatable {\n\n  final LoadDataStatus status;\n  final Account? account;\n\n  const AccountFetchState({\n    this.status = LoadDataStatus.initial,\n    this.account,\n  });\n\n  AccountFetchState copyWith({\n    LoadDataStatus? status,\n    Account? account,\n  }) {\n    return AccountFetchState(\n      status: status ?? this.status,\n      account: account ?? this.account,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, account];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_form/account_form_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport 'package:meta/meta.dart';\nimport '/commons/commons.dart';\nimport '/login/login.dart';\nimport '/accounts/accounts.dart';\n\npart 'account_form_event.dart';\npart 'account_form_state.dart';\n\nclass AccountFormBloc extends Bloc<AccountFormEvent, AccountFormState> {\n\n  final AccountRepository accountRepository;\n  final AuthBloc authBloc;\n\n  AccountFormBloc({\n    required this.accountRepository,\n    required this.authBloc,\n  }) : super(const AccountFormState()) {\n    on<AccountFormCurrencyCodeChanged>(_onCurrencyCodeChanged);\n    on<AccountFormNameChanged>(_onNameChanged);\n    on<AccountFormBalanceChanged>(_onBalanceChanged);\n    on<AccountFormNoChanged>(_onNoChanged);\n    on<AccountFormExpenseableChanged>(_onExpenseableChanged);\n    on<AccountFormIncomeableChanged>(_onIncomeableChanged);\n    on<AccountFormTransferFromAbleChanged>(_onTransferFromAbleChanged);\n    on<AccountFormTransferToAbleChanged>(_onTransferToAbleChanged);\n    on<AccountFormIncludeChanged>(_onIncludeChanged);\n    on<AccountFormNotesChanged>(_onNotesChanged);\n    on<AccountFormLimitChanged>(_onLimitChanged);\n    on<AccountFormBillDayChanged>(_onBillDayChanged);\n    on<AccountFormAprChanged>(_onAprChanged);\n    on<AccountFormSubmitted>(_onSubmitted);\n    on<AccountFormDefaultLoaded>(_onDefaultLoaded);\n  }\n\n  void _onCurrencyCodeChanged(AccountFormCurrencyCodeChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(currencyCode: event.currencyCode),\n    ));\n  }\n\n  void _onNameChanged(AccountFormNameChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(name: event.name),\n    ));\n  }\n\n  void _onBalanceChanged(AccountFormBalanceChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(balance: event.balance),\n    ));\n  }\n\n  void _onNoChanged(AccountFormNoChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(no: event.no),\n    ));\n  }\n\n  void _onExpenseableChanged(AccountFormExpenseableChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(expenseable: event.expenseable),\n    ));\n  }\n\n  void _onIncomeableChanged(AccountFormIncomeableChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(incomeable: event.incomeable),\n    ));\n  }\n\n  void _onTransferFromAbleChanged(AccountFormTransferFromAbleChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(transferFromAble: event.transferFromAble),\n    ));\n  }\n\n  void _onTransferToAbleChanged(AccountFormTransferToAbleChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(transferToAble: event.transferToAble),\n    ));\n  }\n\n  void _onIncludeChanged(AccountFormIncludeChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(include: event.include),\n    ));\n  }\n\n  void _onNotesChanged(AccountFormNotesChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(notes: event.notes),\n    ));\n  }\n\n  void _onLimitChanged(AccountFormLimitChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(limit: event.limit),\n    ));\n  }\n\n  void _onBillDayChanged(AccountFormBillDayChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(billDay: event.billDay),\n    ));\n  }\n\n  void _onAprChanged(AccountFormAprChanged event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(apr: event.apr),\n    ));\n  }\n\n  void _onDefaultLoaded(AccountFormDefaultLoaded event, Emitter<AccountFormState> emit) {\n    emit(state.copyWith(\n      status: FormzStatus.pure,\n      request: state.request.copyWith(\n        currencyCode: event.account?.currencyCode ?? authBloc.state.session!.defaultGroup.defaultCurrencyCode,\n        name: event.account?.name ?? '',\n        balance: event.account != null ? removeDecimalZero(event.account!.balance) : null,\n        no: event.account?.no ?? '',\n        expenseable: event.account?.expenseable ?? event.accountType == 1 || event.accountType == 2 ? true : false,\n        incomeable: event.account?.incomeable ?? event.accountType == 1 ? true : false,\n        transferFromAble: event.account?.transferFromAble ?? true,\n        transferToAble: event.account?.transferToAble ?? event.accountType != 2 ? true : false,\n        include: event.account?.include ?? true,\n        notes: event.account?.notes ?? '',\n        limit: event.account != null && event.account!.limit != null ? removeDecimalZero(event.account!.limit!) : null,\n        billDay: event.account != null && event.account!.billDay != null  ? removeDecimalZero(event.account!.billDay!) : null,\n        apr: event.account != null && event.account!.apr != null  ? removeDecimalZero(event.account!.apr!) : null,\n      )\n    ));\n  }\n\n  void _onSubmitted(AccountFormSubmitted event, Emitter<AccountFormState> emit) async {\n    try {\n      bool result = false;\n      switch (event.type) {\n        case 1:\n          result = await accountRepository.add(event.accountType, state.request);\n          break;\n        case 2:\n          result = await accountRepository.update(event.account!.id, state.request);\n          break;\n        default:\n          break;\n      }\n      if (result) {\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n      } else {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    } catch (_) {\n      emit(state.copyWith(status: FormzStatus.submissionFailure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_form/account_form_event.dart",
    "content": "part of 'account_form_bloc.dart';\n\n@immutable\nabstract class AccountFormEvent extends Equatable {\n  const AccountFormEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass AccountFormCurrencyCodeChanged extends AccountFormEvent {\n  const AccountFormCurrencyCodeChanged(this.currencyCode);\n  final String currencyCode;\n  @override\n  List<Object> get props => [currencyCode];\n}\n\nclass AccountFormNameChanged extends AccountFormEvent {\n  const AccountFormNameChanged(this.name);\n  final String name;\n  @override\n  List<Object> get props => [name];\n}\n\nclass AccountFormBalanceChanged extends AccountFormEvent {\n  const AccountFormBalanceChanged(this.balance);\n  final String balance;\n  @override\n  List<Object> get props => [balance];\n}\n\nclass AccountFormNoChanged extends AccountFormEvent {\n  const AccountFormNoChanged(this.no);\n  final String no;\n  @override\n  List<Object> get props => [no];\n}\n\nclass AccountFormExpenseableChanged extends AccountFormEvent {\n  const AccountFormExpenseableChanged(this.expenseable);\n  final bool expenseable;\n  @override\n  List<Object> get props => [expenseable];\n}\n\nclass AccountFormIncomeableChanged extends AccountFormEvent {\n  const AccountFormIncomeableChanged(this.incomeable);\n  final bool incomeable;\n  @override\n  List<Object> get props => [incomeable];\n}\n\nclass AccountFormTransferFromAbleChanged extends AccountFormEvent {\n  const AccountFormTransferFromAbleChanged(this.transferFromAble);\n  final bool transferFromAble;\n  @override\n  List<Object> get props => [transferFromAble];\n}\n\nclass AccountFormTransferToAbleChanged extends AccountFormEvent {\n  const AccountFormTransferToAbleChanged(this.transferToAble);\n  final bool transferToAble;\n  @override\n  List<Object> get props => [transferToAble];\n}\n\nclass AccountFormIncludeChanged extends AccountFormEvent {\n  const AccountFormIncludeChanged(this.include);\n  final bool include;\n  @override\n  List<Object> get props => [include];\n}\n\nclass AccountFormNotesChanged extends AccountFormEvent {\n  const AccountFormNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass AccountFormLimitChanged extends AccountFormEvent {\n  const AccountFormLimitChanged(this.limit);\n  final String limit;\n  @override\n  List<Object> get props => [limit];\n}\n\nclass AccountFormBillDayChanged extends AccountFormEvent {\n  const AccountFormBillDayChanged(this.billDay);\n  final String billDay;\n  @override\n  List<Object> get props => [billDay];\n}\n\nclass AccountFormAprChanged extends AccountFormEvent {\n  const AccountFormAprChanged(this.apr);\n  final String apr;\n  @override\n  List<Object> get props => [apr];\n}\n\nclass AccountFormDefaultLoaded extends AccountFormEvent {\n  final int type;\n  final int accountType;\n  final Account? account;\n  const AccountFormDefaultLoaded(this.type, this.accountType, this.account);\n  @override\n  List<Object?> get props => [type, accountType, account];\n}\n\nclass AccountFormSubmitted extends AccountFormEvent {\n  final int type;\n  final int accountType;\n  final Account? account;\n  const AccountFormSubmitted(this.type, this.accountType, this.account);\n  @override\n  List<Object?> get props => [type, accountType, account];\n}\n\n\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_form/account_form_state.dart",
    "content": "part of 'account_form_bloc.dart';\n\n@immutable\nclass AccountFormState extends Equatable {\n\n  final FormzStatus status;\n  final AccountFormRequest request;\n\n  const AccountFormState({\n    this.status = FormzStatus.pure,\n    this.request = const AccountFormRequest(),\n  });\n\n  AccountFormState copyWith({\n    FormzStatus? status,\n    AccountFormRequest? request,\n  }) {\n    return AccountFormState(\n      status: status ?? this.status,\n      request: request ?? this.request,\n    );\n  }\n\n  @override\n  List<Object> get props => [status, request];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_incomeable/account_incomeable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\n\npart 'account_incomeable_event.dart';\npart 'account_incomeable_state.dart';\n\nclass AccountIncomeableBloc extends Bloc<AccountIncomeableEvent, AccountIncomeableState> {\n\n  final AccountRepository accountRepository;\n\n  AccountIncomeableBloc({\n    required this.accountRepository\n  }) : super(AccountIncomeableStateLoadInProgress()) {\n    on<AccountIncomeableLoaded>(_onAccountIncomeableLoaded);\n  }\n\n  void _onAccountIncomeableLoaded(_, Emitter<AccountIncomeableState> emit) async {\n    // 只加载一次数据\n    if (state is AccountIncomeableStateLoadSuccess) return;\n    try {\n      emit(AccountIncomeableStateLoadInProgress());\n      final accounts = await accountRepository.getIncomeable();\n      emit(AccountIncomeableStateLoadSuccess(accounts));\n    } catch (_) {\n      emit(AccountIncomeableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_incomeable/account_incomeable_event.dart",
    "content": "part of 'account_incomeable_bloc.dart';\n\nabstract class AccountIncomeableEvent extends Equatable {\n  const AccountIncomeableEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountIncomeableLoaded extends AccountIncomeableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_incomeable/account_incomeable_state.dart",
    "content": "part of 'account_incomeable_bloc.dart';\n\nabstract class AccountIncomeableState extends Equatable {\n  const AccountIncomeableState();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountIncomeableStateLoadInProgress extends AccountIncomeableState { }\n\nclass AccountIncomeableStateLoadSuccess extends AccountIncomeableState {\n  final List<Account> accounts;\n  const AccountIncomeableStateLoadSuccess(this.accounts);\n  @override\n  List<Object> get props => [accounts];\n}\n\nclass AccountIncomeableStateLoadFailure extends AccountIncomeableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_transfer_from_able/account_transfer_from_able_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\n\npart 'account_transfer_from_able_event.dart';\npart 'account_transfer_from_able_state.dart';\n\nclass AccountTransferFromAbleBloc extends Bloc<AccountTransferFromAbleEvent, AccountTransferFromAbleState> {\n\n  final AccountRepository accountRepository;\n\n  AccountTransferFromAbleBloc({\n    required this.accountRepository\n  }) : super(AccountTransferFromAbleStateLoadInProgress()) {\n    on<AccountTransferFromAbleLoaded>(_onAccountTransferFromAbleLoaded);\n  }\n\n  void _onAccountTransferFromAbleLoaded(_, Emitter<AccountTransferFromAbleState> emit) async {\n    // 只加载一次数据\n    if (state is AccountTransferFromAbleStateLoadSuccess) return;\n    try {\n      emit(AccountTransferFromAbleStateLoadInProgress());\n      final accounts = await accountRepository.getTransferFromAble();\n      emit(AccountTransferFromAbleStateLoadSuccess(accounts));\n    } catch (_) {\n      emit(AccountTransferFromAbleStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_transfer_from_able/account_transfer_from_able_event.dart",
    "content": "part of 'account_transfer_from_able_bloc.dart';\n\nabstract class AccountTransferFromAbleEvent extends Equatable {\n  const AccountTransferFromAbleEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountTransferFromAbleLoaded extends AccountTransferFromAbleEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_transfer_from_able/account_transfer_from_able_state.dart",
    "content": "part of 'account_transfer_from_able_bloc.dart';\n\nabstract class AccountTransferFromAbleState extends Equatable {\n  const AccountTransferFromAbleState();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountTransferFromAbleStateLoadInProgress extends AccountTransferFromAbleState { }\n\nclass AccountTransferFromAbleStateLoadSuccess extends AccountTransferFromAbleState {\n  final List<Account> accounts;\n  const AccountTransferFromAbleStateLoadSuccess(this.accounts);\n  @override\n  List<Object> get props => [accounts];\n}\n\nclass AccountTransferFromAbleStateLoadFailure extends AccountTransferFromAbleState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_transfer_to_able/account_transfer_to_able_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\n\npart 'account_transfer_to_able_event.dart';\npart 'account_transfer_to_able_state.dart';\n\nclass AccountTransferToAbleBloc extends Bloc<AccountTransferToAbleEvent, AccountTransferToAbleState> {\n\n  final AccountRepository accountRepository;\n\n  AccountTransferToAbleBloc({\n    required this.accountRepository\n  }) : super(AccountTransferToAbleStateLoadInProgress()) {\n    on<AccountTransferToAbleLoaded>(_onAccountTransferToAbleLoaded);\n  }\n\n  void _onAccountTransferToAbleLoaded(_, Emitter<AccountTransferToAbleState> emit) async {\n    // 只加载一次数据\n    if (state is AccountTransferToAbleStateLoadSuccess) return;\n    try {\n      emit(AccountTransferToAbleStateLoadInProgress());\n      final accounts = await accountRepository.getTransferToAble();\n      emit(AccountTransferToAbleStateLoadSuccess(accounts));\n    } catch (_) {\n      emit(AccountTransferToAbleStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_transfer_to_able/account_transfer_to_able_event.dart",
    "content": "part of 'account_transfer_to_able_bloc.dart';\n\nabstract class AccountTransferToAbleEvent extends Equatable {\n  const AccountTransferToAbleEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountTransferToAbleLoaded extends AccountTransferToAbleEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/account_transfer_to_able/account_transfer_to_able_state.dart",
    "content": "part of 'account_transfer_to_able_bloc.dart';\n\nabstract class AccountTransferToAbleState extends Equatable {\n  const AccountTransferToAbleState();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountTransferToAbleStateLoadInProgress extends AccountTransferToAbleState { }\n\nclass AccountTransferToAbleStateLoadSuccess extends AccountTransferToAbleState {\n  final List<Account> accounts;\n  const AccountTransferToAbleStateLoadSuccess(this.accounts);\n  @override\n  List<Object> get props => [accounts];\n}\n\nclass AccountTransferToAbleStateLoadFailure extends AccountTransferToAbleState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/accounts/accounts_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\npart 'accounts_event.dart';\npart 'accounts_state.dart';\n\nclass AccountsBloc extends Bloc<AccountsEvent, AccountsState> {\n\n  final AccountRepository accountRepository;\n\n  AccountsBloc({\n    required this.accountRepository,\n  }) : super(AccountsState()) {\n    on<AccountsRefreshed>(_onRefreshed);\n    on<AccountsLoadMore>(_onLoadMore);\n    on<AccountsTabChanged>(_onTabChanged);\n    on<AccountsSortChanged>(_onSortChanged);\n    on<AccountDeleted>(_onDeleted);\n    on<AccountToggled>(_onToggled);\n  }\n\n  void _onRefreshed(_, Emitter<AccountsState> emit) async {\n    try {\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n        request: state.request.copyWith(page: 1),\n      ));\n      final accounts = await accountRepository.query(state.request);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        accounts: accounts,\n        request: state.request.copyWith(page: state.request.page + 1),\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onLoadMore(_, Emitter<AccountsState> emit) async {\n    try {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.progress));\n      final accounts = await accountRepository.query(state.request);\n      if (accounts.isNotEmpty) {\n        emit(state.copyWith(\n          request: state.request.copyWith(page: state.request.page + 1),\n          accounts: List.of(state.accounts)..addAll(accounts),\n          loadMoreStatus: LoadDataStatus.success,\n        ));\n      } else {\n        emit(state.copyWith(loadMoreStatus: LoadDataStatus.empty));\n      }\n    } catch (_) {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onTabChanged(AccountsTabChanged event, Emitter<AccountsState> emit) async {\n    emit(state.copyWith(\n      request: state.request.copyWith(type: event.tab+1),\n    ));\n  }\n\n  void _onSortChanged(AccountsSortChanged event, Emitter<AccountsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(sort: event.sort),\n    ));\n  }\n\n  void _onDeleted(AccountDeleted event, Emitter<AccountsState> emit) async {\n    try {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.progress));\n      final result = await accountRepository.delete(event.id);\n      if (result) {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onToggled(AccountToggled event, Emitter<AccountsState> emit) async {\n    try {\n      emit(state.copyWith(toggleStatus: LoadDataStatus.progress));\n      final result = await accountRepository.toggle(event.id);\n      if (result) {\n        emit(state.copyWith(toggleStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(toggleStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(toggleStatus: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/accounts/accounts_event.dart",
    "content": "part of 'accounts_bloc.dart';\n\n@immutable\nabstract class AccountsEvent extends Equatable {\n  const AccountsEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass AccountsRefreshed extends AccountsEvent {}\n\nclass AccountsLoadMore extends AccountsEvent {}\n\nclass AccountDeleted extends AccountsEvent {\n  final String id;\n  const AccountDeleted(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass AccountToggled extends AccountsEvent {\n  final String id;\n  const AccountToggled(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass AccountsTabChanged extends AccountsEvent {\n  const AccountsTabChanged(this.tab);\n  final int tab;\n  @override\n  List<Object> get props => [tab];\n}\n\nclass AccountsSortChanged extends AccountsEvent {\n  const AccountsSortChanged(this.sort);\n  final String sort;\n  @override\n  List<Object> get props => [sort];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/bloc/accounts/accounts_state.dart",
    "content": "part of 'accounts_bloc.dart';\n\n@immutable\nclass AccountsState extends Equatable {\n\n  final LoadDataStatus status;\n  final List<Account> accounts;\n  final AccountQueryRequest request;\n  final LoadDataStatus loadMoreStatus;\n  final LoadDataStatus deleteStatus;\n  final LoadDataStatus toggleStatus;\n\n  const AccountsState({\n    this.status = LoadDataStatus.initial,\n    this.accounts = const <Account>[],\n    this.request = const AccountQueryRequest(),\n    this.loadMoreStatus = LoadDataStatus.initial,\n    this.deleteStatus = LoadDataStatus.initial,\n    this.toggleStatus = LoadDataStatus.initial,\n  });\n\n  AccountsState copyWith({\n    LoadDataStatus? status,\n    List<Account>? accounts,\n    AccountQueryRequest? request,\n    LoadDataStatus? loadMoreStatus,\n    LoadDataStatus? deleteStatus,\n    LoadDataStatus? toggleStatus,\n  }) {\n    return AccountsState(\n      status: status ?? this.status,\n      accounts: accounts ?? this.accounts,\n      request: request ?? this.request,\n      loadMoreStatus: loadMoreStatus ?? this.loadMoreStatus,\n      deleteStatus: deleteStatus ?? this.deleteStatus,\n      toggleStatus: toggleStatus ?? this.toggleStatus\n    );\n  }\n\n  @override\n  List<Object> get props => [status, request, loadMoreStatus, deleteStatus, toggleStatus];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/account_repository.dart",
    "content": "import 'dart:convert';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass AccountRepository {\n\n  List<Account> _responseToList(String response) {\n    return (json.decode(response)['data']).map<Account>((i) => Account.fromJson(i)).toList();\n  }\n\n  Account _responseAccount(String response) {\n    return Account.fromJson(json.decode(response)['data']);\n  }\n\n  List<Account> _responseToList2(String response) {\n    return (json.decode(response)['data']['content']).map<Account>((i) => Account.fromJson(i)).toList();\n  }\n\n  Future<List<Account>> getExpenseable() async {\n    String response = await HttpClient().get('accounts/expenseable');\n    return _responseToList(response);\n  }\n\n  Future<List<Account>> getIncomeable() async {\n    String response = await HttpClient().get('accounts/incomeable');\n    return _responseToList(response);\n  }\n\n  Future<List<Account>> getTransferFromAble() async {\n    String response = await HttpClient().get('accounts/transfer-from-able');\n    return _responseToList(response);\n  }\n\n  Future<List<Account>> getTransferToAble() async {\n    String response = await HttpClient().get('accounts/transfer-to-able');\n    return _responseToList(response);\n  }\n\n  Future<List<Account>> getEnable() async {\n    String response = await HttpClient().get('accounts/enable');\n    return _responseToList(response);\n  }\n\n  Future<List<Account>> query(AccountQueryRequest request) async {\n    switch(request.type) {\n      case 1:\n        return _responseToList2(await HttpClient().get('checking-accounts', params: request.toJson()));\n      case 2:\n        return _responseToList2(await HttpClient().get('credit-accounts', params: request.toJson()));\n      case 3:\n        return _responseToList2(await HttpClient().get('debt-accounts', params: request.toJson()));\n      case 4:\n        return _responseToList2(await HttpClient().get('asset-accounts', params: request.toJson()));\n      default:\n        throw('type error');\n    }\n  }\n\n  Future<Account> get(int id) async {\n    return _responseAccount(await HttpClient().get('accounts/$id'));\n  }\n\n  Future<bool> delete(String id) async {\n    String response = await HttpClient().delete('accounts/$id');\n    return parseResponse(response);\n  }\n\n  Future<bool> toggle(String id) async {\n    String response = await HttpClient().put('accounts/$id/toggle');\n    return parseResponse(response);\n  }\n\n  Future<bool> add(int type, AccountFormRequest request) async {\n    String response = '';\n    switch(type) {\n      case 1:\n        response = await HttpClient().post('checking-accounts', data: request.toJson());\n        return parseResponse(response);\n      case 2:\n        response = await HttpClient().post('credit-accounts', data: request.toJson());\n        return parseResponse(response);\n      case 3:\n        response = await HttpClient().post('debt-accounts', data: request.toJson());\n        return parseResponse(response);\n      case 4:\n        response = await HttpClient().post('asset-accounts', data: request.toJson());\n        return parseResponse(response);\n      default:\n        throw('type error');\n    }\n  }\n\n  Future<bool> update(int id, AccountFormRequest request) async {\n    String response = await HttpClient().put('accounts/$id', data: request.toJson());\n    return parseResponse(response);\n  }\n\n  Future<bool> adjustBalance(int id, AdjustBalanceRequest request) async {\n    String response = await HttpClient().post('accounts/$id/adjust-balance', data: request.toJson());\n    return parseResponse(response);\n  }\n\n  Future<bool> updateAdjustBalance(int id, AdjustBalanceRequest request) async {\n    String response = await HttpClient().put('adjust-balances/$id', data: request.toJson());\n    return parseResponse(response);\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/account.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'account.g.dart';\n\n@JsonSerializable()\nclass Account extends Equatable {\n\n  final int id;\n  final int type;\n  final String typeName;\n  final String name;\n  final String? no;\n  final double balance;\n  final String currencyCode;\n  final double? convertedBalance;\n  final bool enable;\n  final bool include;\n  final bool expenseable;\n  final bool incomeable;\n  final bool transferFromAble;\n  final bool transferToAble;\n  final double initialBalance;\n  final String? notes;\n\n  //credit\n  final double? limit;\n  final int? billDay;\n  final double? remainLimit;\n  final double? apr;\n  final int? asOfDate;\n\n  Account({\n    required this.id,\n    required this.type,\n    required this.typeName,\n    required this.name,\n    required this.balance,\n    required this.currencyCode,\n    required this.convertedBalance,\n    required this.enable,\n    required this.include,\n    required this.expenseable,\n    required this.incomeable,\n    required this.transferFromAble,\n    required this.transferToAble,\n    required this.initialBalance,\n    this.no,\n    this.notes,\n    this.limit,\n    this.billDay,\n    this.remainLimit,\n    this.apr,\n    this.asOfDate\n  });\n\n  factory Account.fromJson(Map<String, dynamic> json) => _$AccountFromJson(json);\n\n  Map<String, dynamic> toJson() => _$AccountToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/account.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'account.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nAccount _$AccountFromJson(Map<String, dynamic> json) => Account(\n      id: json['id'] as int,\n      type: json['type'] as int,\n      typeName: json['typeName'] as String,\n      name: json['name'] as String,\n      balance: (json['balance'] as num).toDouble(),\n      currencyCode: json['currencyCode'] as String,\n      convertedBalance: (json['convertedBalance'] as num?)?.toDouble(),\n      enable: json['enable'] as bool,\n      include: json['include'] as bool,\n      expenseable: json['expenseable'] as bool,\n      incomeable: json['incomeable'] as bool,\n      transferFromAble: json['transferFromAble'] as bool,\n      transferToAble: json['transferToAble'] as bool,\n      initialBalance: (json['initialBalance'] as num).toDouble(),\n      no: json['no'] as String?,\n      notes: json['notes'] as String?,\n      limit: (json['limit'] as num?)?.toDouble(),\n      billDay: json['billDay'] as int?,\n      remainLimit: (json['remainLimit'] as num?)?.toDouble(),\n      apr: (json['apr'] as num?)?.toDouble(),\n      asOfDate: json['asOfDate'] as int?,\n    );\n\nMap<String, dynamic> _$AccountToJson(Account instance) => <String, dynamic>{\n      'id': instance.id,\n      'type': instance.type,\n      'typeName': instance.typeName,\n      'name': instance.name,\n      'no': instance.no,\n      'balance': instance.balance,\n      'currencyCode': instance.currencyCode,\n      'convertedBalance': instance.convertedBalance,\n      'enable': instance.enable,\n      'include': instance.include,\n      'expenseable': instance.expenseable,\n      'incomeable': instance.incomeable,\n      'transferFromAble': instance.transferFromAble,\n      'transferToAble': instance.transferToAble,\n      'initialBalance': instance.initialBalance,\n      'notes': instance.notes,\n      'limit': instance.limit,\n      'billDay': instance.billDay,\n      'remainLimit': instance.remainLimit,\n      'apr': instance.apr,\n      'asOfDate': instance.asOfDate,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/account_form_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'account_form_request.g.dart';\n\n@JsonSerializable()\nclass AccountFormRequest extends Equatable {\n\n  final String? currencyCode;\n  final String? name;\n  final String? no; // 卡号\n  final String? balance;\n  final bool? include;\n  final bool? transferFromAble;\n  final bool? transferToAble;\n  final bool? expenseable;\n  final bool? incomeable;\n  final String? notes;\n  final String? limit;\n  final String? billDay;\n  final String? apr;\n\n  const AccountFormRequest({\n    this.currencyCode,\n    this.name,\n    this.no,\n    this.balance,\n    this.include,\n    this.transferFromAble,\n    this.transferToAble,\n    this.expenseable,\n    this.incomeable,\n    this.notes,\n    this.limit,\n    this.billDay,\n    this.apr,\n  });\n\n  Map<String, dynamic> toJson() => _$AccountFormRequestToJson(this);\n\n  AccountFormRequest copyWith({\n    String? currencyCode,\n    String? name,\n    String? no,\n    String? balance,\n    bool? include,\n    bool? transferFromAble,\n    bool? transferToAble,\n    bool? expenseable,\n    bool? incomeable,\n    String? notes,\n    String? limit,\n    String? billDay,\n    String? apr,\n  }) {\n    return AccountFormRequest(\n      currencyCode: currencyCode ?? this.currencyCode,\n      name: name ?? this.name,\n      no: no ?? this.no,\n      balance: balance ?? this.balance,\n      include: include ?? this.include,\n      transferFromAble: transferFromAble ?? this.transferFromAble,\n      transferToAble: transferToAble ?? this.transferToAble,\n      expenseable: expenseable ?? this.expenseable,\n      incomeable: incomeable ?? this.incomeable,\n      notes: notes ?? this.notes,\n      limit: limit ?? this.limit,\n      billDay: billDay ?? this.billDay,\n      apr: apr ?? this.apr,\n    );\n  }\n\n  @override\n  List<Object?> get props => [currencyCode, name, no, balance, expenseable, incomeable, transferFromAble, transferToAble, include, notes, limit, billDay, apr];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/account_form_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'account_form_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nAccountFormRequest _$AccountFormRequestFromJson(Map<String, dynamic> json) =>\n    AccountFormRequest(\n      currencyCode: json['currencyCode'] as String?,\n      name: json['name'] as String?,\n      no: json['no'] as String?,\n      balance: json['balance'] as String?,\n      include: json['include'] as bool?,\n      transferFromAble: json['transferFromAble'] as bool?,\n      transferToAble: json['transferToAble'] as bool?,\n      expenseable: json['expenseable'] as bool?,\n      incomeable: json['incomeable'] as bool?,\n      notes: json['notes'] as String?,\n      limit: json['limit'] as String?,\n      billDay: json['billDay'] as String?,\n      apr: json['apr'] as String?,\n    );\n\nMap<String, dynamic> _$AccountFormRequestToJson(AccountFormRequest instance) =>\n    <String, dynamic>{\n      'currencyCode': instance.currencyCode,\n      'name': instance.name,\n      'no': instance.no,\n      'balance': instance.balance,\n      'include': instance.include,\n      'transferFromAble': instance.transferFromAble,\n      'transferToAble': instance.transferToAble,\n      'expenseable': instance.expenseable,\n      'incomeable': instance.incomeable,\n      'notes': instance.notes,\n      'limit': instance.limit,\n      'billDay': instance.billDay,\n      'apr': instance.apr,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/account_query_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'account_query_request.g.dart';\n\n@JsonSerializable()\nclass AccountQueryRequest extends Equatable {\n\n  final int type;\n  final int page;\n  final int size;\n  final String sort;\n\n  const AccountQueryRequest({\n    this.type = 1,\n    this.page = 1,\n    this.size = 10,\n    this.sort = ''\n  });\n\n  factory AccountQueryRequest.fromJson(Map<String, dynamic> json) => _$AccountQueryRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$AccountQueryRequestToJson(this);\n\n  @override\n  List<Object?> get props => [type, page, sort];\n\n  AccountQueryRequest copyWith({\n    int? type,\n    int? page,\n    int? size,\n    String? sort,\n  }) {\n    return AccountQueryRequest(\n      type: type ?? this.type,\n      page: page ?? this.page,\n      size: size ?? this.size,\n      sort: sort ?? this.sort,\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/account_query_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'account_query_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nAccountQueryRequest _$AccountQueryRequestFromJson(Map<String, dynamic> json) =>\n    AccountQueryRequest(\n      type: json['type'] as int? ?? 1,\n      page: json['page'] as int? ?? 1,\n      size: json['size'] as int? ?? 10,\n      sort: json['sort'] as String? ?? '',\n    );\n\nMap<String, dynamic> _$AccountQueryRequestToJson(\n        AccountQueryRequest instance) =>\n    <String, dynamic>{\n      'type': instance.type,\n      'page': instance.page,\n      'size': instance.size,\n      'sort': instance.sort,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/adjust_balance_request.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'adjust_balance_request.g.dart';\n\n@JsonSerializable()\nclass AdjustBalanceRequest extends Equatable {\n\n  final String? description;\n  final int? createTime;\n  final String? balance;\n  final String? convertedBalance;\n  final String? notes;\n\n  const AdjustBalanceRequest({\n    this.description,\n    this.createTime,\n    this.balance,\n    this.convertedBalance,\n    this.notes,\n  });\n\n  AdjustBalanceRequest copyWith({\n    String? description,\n    int? createTime,\n    String? balance,\n    String? convertedBalance,\n    String? notes,\n  }) {\n    return AdjustBalanceRequest(\n      description: description ?? this.description,\n      createTime: createTime ?? this.createTime,\n      balance: balance ?? this.balance,\n      convertedBalance: convertedBalance ?? this.convertedBalance,\n      notes: notes ?? this.notes,\n    );\n  }\n\n  factory AdjustBalanceRequest.fromJson(Map<String, dynamic> json) => _$AdjustBalanceRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$AdjustBalanceRequestToJson(this);\n\n  @override\n  List<Object?> get props => [description, createTime, balance, convertedBalance, notes];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/adjust_balance_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'adjust_balance_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nAdjustBalanceRequest _$AdjustBalanceRequestFromJson(\n        Map<String, dynamic> json) =>\n    AdjustBalanceRequest(\n      description: json['description'] as String?,\n      createTime: json['createTime'] as int?,\n      balance: json['balance'] as String?,\n      convertedBalance: json['convertedBalance'] as String?,\n      notes: json['notes'] as String?,\n    );\n\nMap<String, dynamic> _$AdjustBalanceRequestToJson(\n        AdjustBalanceRequest instance) =>\n    <String, dynamic>{\n      'description': instance.description,\n      'createTime': instance.createTime,\n      'balance': instance.balance,\n      'convertedBalance': instance.convertedBalance,\n      'notes': instance.notes,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/data/models/credit_account.dart",
    "content": "// import 'package:json_annotation/json_annotation.dart';\n// import 'account.dart';\n//\n// part 'credit_account.g.dart';\n//\n// @JsonSerializable()\n//\n//\n// class CreditAccount extends Account {\n//\n//   final double limit;\n//   final int billDay;\n// }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/account_adjust_balance.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\n\nclass AccountAdjustBalance extends StatelessWidget {\n\n  final Account account;\n\n  AccountAdjustBalance({\n    required this.account,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n          title: Text('调整账户余额'),\n          actions: [\n            Builder(builder: (context) {\n              return IconButton(\n                icon: Icon(Icons.done),\n                onPressed: () {\n                  BlocProvider.of<AccountAdjustBalanceBloc>(context).add(AccountAdjustBalanceSubmitted(1, account.id, null));\n                }\n              );\n            })\n          ]\n      ),\n      body: AdjustBalanceForm(type: 1, account: account),\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/account_detail_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/login/login.dart';\n\nclass AccountDetailPage extends StatefulWidget {\n\n  final Account account;\n  AccountDetailPage({\n    required this.account\n  });\n\n  @override\n  State<AccountDetailPage> createState() => _AccountDetailPageState();\n}\n\nclass _AccountDetailPageState extends State<AccountDetailPage> {\n\n  @override\n  void initState() {\n    BlocProvider.of<AccountFetchBloc>(context).add(AccountLoadDefault(account: widget.account));\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<AccountsBloc, AccountsState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              // Navigator.pop(context);\n              if (Navigator.canPop(context)) {\n                Navigator.of(context).pop();\n              } else {\n                SystemNavigator.pop();\n              }\n            }\n          }\n        ),\n        BlocListener<AccountsBloc, AccountsState>(\n          listenWhen: (previous, current) => previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              BlocProvider.of<AccountFetchBloc>(context).add(AccountFetched());\n            }\n          },\n        )\n      ],\n      child: BlocBuilder<AccountFetchBloc, AccountFetchState>(\n        builder: (context, state) {\n          return Scaffold(\n            appBar: AppBar(\n              centerTitle: true,\n              title: const Text('账户详情'),\n              actions: _buildActions(context, state.account ?? widget.account)\n            ),\n            body: Builder(\n              builder: (context) {\n                switch (state.status) {\n                  case LoadDataStatus.progress:\n                  case LoadDataStatus.initial:\n                    return const PageLoading();\n                  case LoadDataStatus.success:\n                    return _buildBody(context, state.account ?? widget.account);\n                  default:\n                    return PageError(onTap: () { BlocProvider.of<AccountFetchBloc>(context).add(AccountFetched()); });\n                }\n              },\n            )\n          );\n        }\n      )\n    );\n  }\n\n  List<Widget> _buildActions(BuildContext context, Account account) {\n    return [\n      IconButton(\n        icon: Icon(Icons.edit),\n        onPressed: () {\n          fullDialog(context, AccountFormPage(type: 2, accountType: account.type, account: account));\n        }\n      ),\n      IconButton(\n        icon: Icon(Icons.delete),\n        onPressed: () async {\n          if (await confirm(\n            context,\n            content: Text(\"确定删除${account.name}吗？\"),\n            textOK: Text(\"确定\"),\n            textCancel: Text(\"取消\"),\n          )) {\n            BlocProvider.of<AccountsBloc>(context).add(AccountDeleted(account.id.toString()));\n          }\n        }\n      )\n    ];\n  }\n\n  Widget _buildBody(BuildContext context, Account account) {\n    final theme = Theme.of(context);\n    TextStyle? style1 = theme.textTheme.bodyText2;\n    TextStyle? style2 = theme.textTheme.bodyText1;\n    final state = context.watch<AuthBloc>().state;\n    return SingleChildScrollView(\n      child: Padding(\n        padding: EdgeInsets.symmetric(horizontal: 15),\n        child: Column(\n          children: [\n            SizedBox(height: 20),\n            Row(children: [Text(\"账户类型：\", style: style1), Text(account.typeName, style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"账户名称：\", style: style1), Text(account.name, style: style2)]),\n            Row(children: [\n              Text(\"余额：\", style: style1),\n              Text(removeDecimalZero(account.balance), style: style2),\n              SizedBox(width: 10),\n              TextButton(\n                  child: const Text('余额调整'),\n                  onPressed: () {\n                    fullDialog(context, AccountAdjustBalance(account: account));\n                  }\n              ),\n            ]),\n            Row(children: [Text(\"币种：\", style: style1), Text(account.currencyCode, style: style2)]),\n            SizedBox(height: 15),\n            if (account.currencyCode != state.session?.defaultGroup.defaultCurrencyCode)\n              Row(children: [Text(\"折合${state.session?.defaultGroup.defaultCurrencyCode}：\", style: style1), Text(removeDecimalZero(account.convertedBalance!), style: style2)]),\n            if (account.currencyCode != state.session?.defaultGroup.defaultCurrencyCode) SizedBox(height: 15),\n            if (account.limit != null) Row(children: [Text(\"信用额度：\", style: style1), Text(removeDecimalZero(account.limit!), style: style2)]),\n            if (account.limit != null) SizedBox(height: 15),\n            if (account.remainLimit != null) Row(children: [Text(\"剩余额度：\", style: style1), Text(removeDecimalZero(account.remainLimit!), style: style2)]),\n            if (account.remainLimit != null) SizedBox(height: 15),\n            if (account.type == 2) Row(children: [Text(\"账单日：\", style: style1), Text(account.billDay?.toString() ?? '', style: style2)]),\n            if (account.type == 2) SizedBox(height: 15),\n            if (account.type == 3) Row(children: [Text(\"年化利率(%)：\", style: style1), Text(account.apr?.toString() ?? '', style: style2)]),\n            if (account.type == 3) SizedBox(height: 15),\n            if (account.type == 4) Row(children: [Text(\"更新日期：\", style: style1), Text(dateFormat(account.asOfDate), style: style2)]),\n            if (account.type == 4) SizedBox(height: 15),\n            Row(children: [Text(\"是否可用：\", style: style1), Text(boolToString(account.enable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否计入净资产：\", style: style1), Text(boolToString(account.include), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可支出：\", style: style1), Text(boolToString(account.expenseable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可收入：\", style: style1), Text(boolToString(account.incomeable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可转入：\", style: style1), Text(boolToString(account.transferToAble), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可转出：\", style: style1), Text(boolToString(account.transferFromAble), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"初始余额：\", style: style1), Text(removeDecimalZero(account.initialBalance), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"备注：\", style: style1), Flexible(child: Text(account.notes ?? '', style: style2))]),\n            account.no != null && account.no!.isNotEmpty ? SizedBox(height: 0) : SizedBox(height: 15),\n            Row(children: [\n              Text(\"卡号：\", style: style1),\n              Flexible(child: Text(account.no ?? '', style: style2)),\n              SizedBox(width: 10),\n              account.no != null && account.no!.isNotEmpty ? TextButton(\n                  child: const Text('复制卡号'),\n                  onPressed: () {\n                    Clipboard.setData(ClipboardData(text: account.no)).then((_) => Message.success('卡号复制成功'));\n                  }\n              ) : Text(''),\n            ]),\n            account.no != null && account.no!.isNotEmpty ? SizedBox(height: 0) : SizedBox(height: 15),\n            SizedBox(height: 5),\n            SizedBox(\n              width: double.infinity,\n              child: ElevatedButton(\n                  child: Text(account.enable ? '禁用' : '启用'),\n                  onPressed: () {\n                    BlocProvider.of<AccountsBloc>(context).add(AccountToggled(account.id.toString()));\n                  }\n              ),\n            ),\n            SizedBox(height: 15),\n          ],\n        )\n      ),\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/account_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/login/login.dart';\n\nclass AccountFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final int accountType;\n  final Account? account;\n\n  const AccountFormPage({\n    required this.type,\n    required this.accountType,\n    this.account,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => AccountFormBloc(\n          accountRepository: RepositoryProvider.of<AccountRepository>(context),\n          authBloc: BlocProvider.of<AuthBloc>(context),\n        )..add(AccountFormDefaultLoaded(type, accountType, account)\n      ),\n      child: BlocListener<AccountFormBloc, AccountFormState>(\n        listener: (context, state) {\n          if (state.status == FormzStatus.submissionSuccess) {\n            Message.success('操作成功');\n            Navigator.of(context).pop();\n            BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n            if (type == 2) {\n              BlocProvider.of<AccountFetchBloc>(context).add(AccountFetched());\n            }\n          }\n        },\n        child: Builder(\n          builder: (context) {\n            switch (accountType) {\n              case 1:\n                return CheckingAccountFormPage(type: type, accountType: accountType, account: account);\n              case 2:\n                return CreditAccountFormPage(type: type, accountType: accountType, account: account);\n              case 3:\n                return DebtAccountFormPage(type: type, accountType: accountType, account: account);\n              case 4:\n                return AssetAccountFormPage(type: type, accountType: accountType, account: account);\n              default:\n                return PageError(msg: '账户类型错误');\n            }\n          },\n        )\n      )\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/accounts_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:pull_to_refresh/pull_to_refresh.dart';\nimport '/routes.dart';\nimport '/commons/commons.dart';\nimport '/components/components.dart';\nimport '/accounts/accounts.dart';\n\nclass AccountsPage extends StatefulWidget {\n  @override\n  State<AccountsPage> createState() => _AccountsPageState();\n}\n\n// https://stackoverflow.com/questions/68013459/how-to-use-multiple-tab-for-single-page-in-flutter\nclass _AccountsPageState extends State<AccountsPage> with TickerProviderStateMixin {\n\n  late TabController tabController;\n  RefreshController _refreshController = RefreshController(initialRefresh: false);\n\n  @override\n  void initState() {\n    BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n    tabController = TabController(length: 4, vsync: this);\n    tabController.addListener(() {\n      if(!tabController.indexIsChanging) {\n        BlocProvider.of<AccountsBloc>(context).add(AccountsTabChanged(tabController.index));\n        BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n      }\n    });\n    super.initState();\n  }\n\n  @override\n  void dispose() {\n    tabController.dispose();\n    super.dispose();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<AccountsBloc, AccountsState>(\n          listenWhen: (previous, current) =>\n          previous.loadMoreStatus != current.loadMoreStatus,\n          listener: (context, state) {\n            if (state.loadMoreStatus == LoadDataStatus.success) {\n              _refreshController.loadComplete();\n            } else if (state.loadMoreStatus == LoadDataStatus.failure) {\n              _refreshController.loadFailed();\n            } else if (state.loadMoreStatus == LoadDataStatus.empty) {\n              _refreshController.loadNoData();\n            }\n          },\n        ),\n        BlocListener<AccountsBloc, AccountsState>(\n          listenWhen: (previous, current) =>\n          previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n            }\n          },\n        ),\n        BlocListener<AccountsBloc, AccountsState>(\n          listenWhen: (previous, current) =>\n          previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n            }\n          },\n        ),\n      ],\n      child: Scaffold(\n        appBar: AppBar(\n          leading: IconButton(\n            onPressed: () {\n              BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n            },\n            icon: const Icon(Icons.refresh)\n          ),\n          centerTitle: true,\n          title: TabBar(\n            controller: tabController,\n            labelPadding: EdgeInsets.all(0),\n            tabs: [\n              Tab(child: Text('活期', softWrap: false)),\n              Tab(child: Text('信用', softWrap: false)),\n              Tab(child: Text('贷款', softWrap: false)),\n              Tab(child: Text('资产', softWrap: false)),\n            ],\n          ),\n          actions: [\n            OrderButton(),\n            IconButton(\n              icon: const Icon(Icons.add),\n              onPressed: () {\n                fullDialog(context, AccountFormPage(type: 1, accountType: tabController.index + 1));\n              }\n            )\n          ]\n        ),\n        body: BlocBuilder<AccountsBloc, AccountsState>(\n          buildWhen: (previous, current) => previous.status != current.status || previous.accounts != current.accounts,\n          builder: (context, state) {\n            switch (state.status) {\n              case LoadDataStatus.progress:\n              case LoadDataStatus.initial:\n                return const PageLoading();\n              case LoadDataStatus.success:\n                if (state.accounts.isEmpty) return Empty();\n                return SmartRefresher(\n                  enablePullDown: true,\n                  enablePullUp: true,\n                  controller: _refreshController,\n                  child: _buildList(context, state.accounts),\n                  onRefresh: () async {\n                    BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n                    _refreshController.refreshCompleted();\n                  },\n                  onLoading: () async {\n                    BlocProvider.of<AccountsBloc>(context).add(AccountsLoadMore());\n                  },\n                );\n              default:\n                return PageError(onTap: () {\n                  BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n                });\n            }\n          }\n        )\n      )\n    );\n  }\n\n  Widget _buildList(BuildContext context, List<Account> accounts) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      itemCount: accounts.length,\n      itemBuilder: (context, index) {\n        Account account = accounts[index];\n        return ListTile(\n          dense: false,\n          title: Text(account.name, style: theme.textTheme.bodyText1),\n          trailing: Row(\n            mainAxisSize: MainAxisSize.min,\n            children: [\n              Text(account.balance.toStringAsFixed(2), style: TextStyle(fontWeight: FontWeight.w500, fontSize: 17)),\n              Icon(Icons.keyboard_arrow_right)\n            ],\n          ),\n          onTap: () {\n            Navigator.pushNamed(context, '/account-detail', arguments: AccountDetailArguments(account: account));\n          },\n          onLongPress: () {\n            fullDialog(context, AccountAdjustBalance(account: account));\n          },\n        );\n      },\n      separatorBuilder: (context, index) {\n        return Divider();\n      },\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/asset_account_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/accounts/accounts.dart';\nimport '/login/login.dart';\n\nclass AssetAccountFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final int accountType;\n  final Account? account;\n\n  const AssetAccountFormPage({\n    required this.type,\n    required this.accountType,\n    this.account\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    final authState = context.watch<AuthBloc>().state;\n    final state = context.watch<AccountFormBloc>().state;\n    return Scaffold(\n      appBar: AppBar(\n        centerTitle: true,\n        title: Text(_buildTitle(type)),\n        actions: [\n          IconButton(\n            icon: Icon(Icons.done),\n            onPressed: () {\n              BlocProvider.of<AccountFormBloc>(context).add(AccountFormSubmitted(type, accountType, account));\n            }\n          )\n        ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              if (type == 1) CurrencyInput(),\n              NameInput(),\n              if (type == 1) SizedBox(height: 10),\n              if (type == 1) AccountBalanceInput(),\n              if (type == 1) SizedBox(height: 10),\n              ExpenseableSwitch(),\n              SizedBox(height: 10),\n              IncomeableSwitch(),\n              SizedBox(height: 10),\n              TransferFromAbleSwitch(),\n              SizedBox(height: 10),\n              TransferToAbleSwitch(),\n              SizedBox(height: 10),\n              IncludeSwitch(),\n              SizedBox(height: 10),\n              NotesInput()\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增资产账户';\n      case 2:\n        return '修改资产账户';\n      default:\n        return '账户操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/checking_account_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/accounts/accounts.dart';\nimport '/login/login.dart';\n\nclass CheckingAccountFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final int accountType;\n  final Account? account;\n\n  const CheckingAccountFormPage({\n    required this.type,\n    required this.accountType,\n    this.account\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    final authState = context.watch<AuthBloc>().state;\n    final state = context.watch<AccountFormBloc>().state;\n    return Scaffold(\n      appBar: AppBar(\n          centerTitle: true,\n          title: Text(_buildTitle(type)),\n          actions: [\n            IconButton(\n              icon: Icon(Icons.done),\n              onPressed: () {\n                BlocProvider.of<AccountFormBloc>(context).add(AccountFormSubmitted(type, accountType, account));\n              }\n            )\n          ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              if (type == 1) CurrencyInput(),\n              NameInput(),\n              if (type == 1) SizedBox(height: 10),\n              if (type == 1) AccountBalanceInput(),\n              if (type == 1) SizedBox(height: 10),\n              NoInput(),\n              SizedBox(height: 10),\n              ExpenseableSwitch(),\n              SizedBox(height: 10),\n              IncomeableSwitch(),\n              SizedBox(height: 10),\n              TransferFromAbleSwitch(),\n              SizedBox(height: 10),\n              TransferToAbleSwitch(),\n              SizedBox(height: 10),\n              IncludeSwitch(),\n              SizedBox(height: 10),\n              NotesInput()\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增活期账户';\n      case 2:\n        return '修改活期账户';\n      default:\n        return '账户操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/credit_account_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/accounts/accounts.dart';\nimport '/login/login.dart';\n\nclass CreditAccountFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final int accountType;\n  final Account? account;\n\n  const CreditAccountFormPage({\n    required this.type,\n    required this.accountType,\n    this.account\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    final authState = context.watch<AuthBloc>().state;\n    final state = context.watch<AccountFormBloc>().state;\n    return Scaffold(\n      appBar: AppBar(\n          centerTitle: true,\n          title: Text(_buildTitle(type)),\n          actions: [\n            IconButton(\n              icon: Icon(Icons.done),\n              onPressed: () {\n                BlocProvider.of<AccountFormBloc>(context).add(AccountFormSubmitted(type, accountType, account));\n              }\n            )\n          ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              if (type == 1) CurrencyInput(),\n              NameInput(),\n              if (type == 1) SizedBox(height: 10),\n              if (type == 1) AccountBalanceInput(),\n              if (type == 1) SizedBox(height: 10),\n              AccountLimitInput(),\n              SizedBox(height: 10),\n              AccountBillDayInput(),\n              SizedBox(height: 10),\n              NoInput(),\n              SizedBox(height: 10),\n              ExpenseableSwitch(),\n              SizedBox(height: 10),\n              IncomeableSwitch(),\n              SizedBox(height: 10),\n              TransferFromAbleSwitch(),\n              SizedBox(height: 10),\n              TransferToAbleSwitch(),\n              SizedBox(height: 10),\n              IncludeSwitch(),\n              SizedBox(height: 10),\n              NotesInput()\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增信用账户';\n      case 2:\n        return '修改信用账户';\n      default:\n        return '账户操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/debt_account_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/accounts/accounts.dart';\nimport '/login/login.dart';\n\nclass DebtAccountFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final int accountType;\n  final Account? account;\n\n  const DebtAccountFormPage({\n    required this.type,\n    required this.accountType,\n    this.account\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    final authState = context.watch<AuthBloc>().state;\n    final state = context.watch<AccountFormBloc>().state;\n    return Scaffold(\n      appBar: AppBar(\n          centerTitle: true,\n          title: Text(_buildTitle(type)),\n          actions: [\n            IconButton(\n              icon: Icon(Icons.done),\n              onPressed: () {\n                BlocProvider.of<AccountFormBloc>(context).add(AccountFormSubmitted(type, accountType, account));\n              }\n            )\n          ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              if (type == 1) CurrencyInput(),\n              NameInput(),\n              if (type == 1) SizedBox(height: 10),\n              if (type == 1) AccountBalanceInput(),\n              if (type == 1) SizedBox(height: 10),\n              AccountLimitInput(),\n              SizedBox(height: 10),\n              AccountAprInput(),\n              SizedBox(height: 10),\n              NoInput(),\n              SizedBox(height: 10),\n              ExpenseableSwitch(),\n              SizedBox(height: 10),\n              IncomeableSwitch(),\n              SizedBox(height: 10),\n              TransferFromAbleSwitch(),\n              SizedBox(height: 10),\n              TransferToAbleSwitch(),\n              SizedBox(height: 10),\n              IncludeSwitch(),\n              SizedBox(height: 10),\n              NotesInput()\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增贷款账户';\n      case 2:\n        return '修改贷款账户';\n      default:\n        return '账户操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/account_apr_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass AccountAprInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '年化利率',\n        BlocSelector<AccountFormBloc, AccountFormState, String?>(\n          selector: (state) => state.request.apr,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormAprChanged(value)),\n                keyboardType: TextInputType.number,\n                decoration: InputDecoration(\n                  contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                ),\n              );\n            }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/account_balance_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass AccountBalanceInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '余额',\n        BlocSelector<AccountFormBloc, AccountFormState, String?>(\n          selector: (state) => state.request.balance,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormBalanceChanged(value)),\n                keyboardType: TextInputType.number,\n                decoration: InputDecoration(\n                  contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                ),\n              );\n            }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/account_billday_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass AccountBillDayInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '账单日',\n          BlocSelector<AccountFormBloc, AccountFormState, String?>(\n            selector: (state) => state.request.billDay,\n            builder: (context, state) {\n              controller.value = TextEditingValue(\n                text: state ?? '',\n                selection: TextSelection.fromPosition(\n                  TextPosition(offset: state?.length ?? 0),\n                ),\n              );\n              return\n                TextField(\n                  controller: controller,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormBillDayChanged(value)),\n                  keyboardType: TextInputType.number,\n                  decoration: InputDecoration(\n                    contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                  ),\n                );\n              }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/account_limit_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass AccountLimitInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '额度',\n          BlocSelector<AccountFormBloc, AccountFormState, String?>(\n            selector: (state) => state.request.limit,\n            builder: (context, state) {\n              controller.value = TextEditingValue(\n                text: state ?? '',\n                selection: TextSelection.fromPosition(\n                  TextPosition(offset: state?.length ?? 0),\n                ),\n              );\n              return\n                TextField(\n                  controller: controller,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormLimitChanged(value)),\n                  keyboardType: TextInputType.number,\n                  decoration: InputDecoration(\n                    contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                  ),\n                );\n              }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/currency_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/currency/currency.dart';\n\nclass CurrencyInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<CurrencyAllBloc>().state;\n    return BlocSelector<AccountFormBloc, AccountFormState, String?>(\n        selector: (state) => state.request.currencyCode,\n        builder: (context, state) {\n          return\n            SmartSelect<String>.single(\n                key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n                title: '币种',\n                selectedValue: state ?? '',\n                onChange: (selected) {\n                  context.read<AccountFormBloc>().add(AccountFormCurrencyCodeChanged(selected.value ?? ''));\n                },\n                choiceItems: state1 is CurrencyAllStateLoadSuccess ? modelToChoiceCode(state1.currencies) : [],\n                choiceType: S2ChoiceType.chips,\n                modalFilter: true,\n                modalFilterAuto: true,\n                tileBuilder: (context, state) {\n                  return S2Tile.fromState(\n                    state,\n                    isLoading: state1 is CurrencyAllStateLoadInProgress,\n                    padding: EdgeInsets.zero,\n                  );\n                }\n            );\n        }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/expenseable_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\nimport '/commons/commons.dart';\n\nclass ExpenseableSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可支出\",\n        BlocSelector<AccountFormBloc, AccountFormState, bool?>(\n          selector: (state) => state.request.expenseable,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormExpenseableChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/inclue_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\nimport '/commons/commons.dart';\n\nclass IncludeSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否计入净资产\",\n        BlocSelector<AccountFormBloc, AccountFormState, bool?>(\n          selector: (state) => state.request.include,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormIncludeChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/incomeable_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\nimport '/commons/commons.dart';\n\nclass IncomeableSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可收入\",\n        BlocSelector<AccountFormBloc, AccountFormState, bool?>(\n          selector: (state) => state.request.incomeable,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormIncomeableChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/name_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass NameInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n          '名称',\n          BlocBuilder<AccountFormBloc, AccountFormState>(\n            buildWhen: (previous, current) => previous.request.name != current.request.name,\n            builder: (context, state) {\n              controller.value = TextEditingValue(\n                text: state.request.name ?? '',\n                selection: TextSelection.fromPosition(\n                  TextPosition(offset: state.request.name?.length ?? 0),\n                ),\n              );\n              return\n                TextField(\n                  controller: controller,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormNameChanged(value)),\n                  decoration: InputDecoration(\n                    contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                  ),\n                );\n            }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/no_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/accounts/accounts.dart';\nimport '/commons/commons.dart';\n\nclass NoInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n          '卡号',\n          BlocBuilder<AccountFormBloc, AccountFormState>(\n            buildWhen: (previous, current) => previous.request.no != current.request.no,\n            builder: (context, state) {\n              controller.value = TextEditingValue(\n                text: state.request.no ?? '',\n                selection: TextSelection.fromPosition(\n                  TextPosition(offset: state.request.no?.length ?? 0),\n                ),\n              );\n              return\n                TextField(\n                  controller: controller,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormNoChanged(value)),\n                  decoration: InputDecoration(),\n                );\n            }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/notes_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/accounts/accounts.dart';\nimport '/commons/commons.dart';\n\nclass NotesInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n          '备注',\n          BlocBuilder<AccountFormBloc, AccountFormState>(\n              buildWhen: (previous, current) => previous.request.notes != current.request.notes,\n              builder: (context, state) {\n                controller.value = TextEditingValue(\n                  text: state.request.notes ?? '',\n                  selection: TextSelection.fromPosition(\n                    TextPosition(offset: state.request.notes?.length ?? 0),\n                  ),\n                );\n                return\n                  TextField(\n                    controller: controller,\n                    onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormNotesChanged(value)),\n                    decoration: InputDecoration(),\n                  );\n              }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/transfer_from_able_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\nimport '/commons/commons.dart';\n\nclass TransferFromAbleSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可转入\",\n        BlocSelector<AccountFormBloc, AccountFormState, bool?>(\n          selector: (state) => state.request.transferFromAble,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormTransferFromAbleChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/account_form/transfer_to_able_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/accounts/accounts.dart';\nimport '/commons/commons.dart';\n\nclass TransferToAbleSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可转出\",\n        BlocSelector<AccountFormBloc, AccountFormState, bool?>(\n          selector: (state) => state.request.transferToAble,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<AccountFormBloc>().add(AccountFormTransferToAbleChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/adjust_balance/adjust_balance_form.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\n\nimport '/accounts/accounts.dart';\nimport '/flows/flows.dart';\nimport '/commons/commons.dart';\n\nclass AdjustBalanceForm extends StatefulWidget {\n\n  final int type; // 1-新增，2-修改，3-复制，4-退款\n  final Account? account;\n  final AdjustBalance? adjustBalance;\n\n  AdjustBalanceForm({\n    required this.type,\n    this.account,\n    this.adjustBalance\n  });\n\n  @override\n  State<AdjustBalanceForm> createState() => _AdjustBalanceFormState();\n}\n\nclass _AdjustBalanceFormState extends State<AdjustBalanceForm> {\n\n  @override\n  void initState() {\n    BlocProvider.of<AccountAdjustBalanceBloc>(context).add(AccountAdjustBalanceDefaultLoaded(widget.type, widget.adjustBalance));\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<AccountAdjustBalanceBloc, AccountAdjustBalanceState>(\n      listenWhen: (previous, current) => previous.status != current.status,\n      listener: (context, state) {\n        if (state.status == FormzStatus.submissionSuccess) {\n          Message.success('操作成功');\n          Navigator.of(context).pop();\n          BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n          if (widget.type == 1) {\n            BlocProvider.of<AccountFetchBloc>(context).add(AccountFetched());\n            BlocProvider.of<AccountsBloc>(context).add(AccountsRefreshed());\n          }\n          // 如果是修改和退款，要刷新账单详情页\n          if (widget.type == 2) {\n            BlocProvider.of<FlowFetchBloc>(context).add(FlowFetched());\n          }\n        }\n      },\n      child: SingleChildScrollView(\n          child: Padding(\n            padding: EdgeInsets.symmetric(horizontal: 15),\n            child: Column(\n              children: [\n                DescriptionInput(),\n                SizedBox(height: 10),\n                AdjustBlanceDateTimeInput(),\n                SizedBox(height: 10),\n                if (widget.type == 1) BalanceInput(currentBalance: widget.account!.balance),\n                SizedBox(height: 10),\n                NoteInput()\n              ],\n            ),\n          )\n      )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/adjust_balance/balance_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass BalanceInput extends StatelessWidget {\n\n  final num currentBalance;\n\n  BalanceInput({\n    required this.currentBalance,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return Container(\n      height: 45,\n      child: Row(\n        mainAxisAlignment: MainAxisAlignment.spaceBetween,\n        children: [\n          Text('余额', style: Theme.of(context).textTheme.bodyText1),\n          SizedBox(width: 10),\n          Expanded(child: BlocSelector<AccountAdjustBalanceBloc, AccountAdjustBalanceState, String?>(\n            selector: (state) => state.request.balance,\n            builder: (context, state) {\n              controller.value = TextEditingValue(\n                text: state ?? '',\n                selection: TextSelection.fromPosition(\n                  TextPosition(offset: state?.length ?? 0),\n                ),\n              );\n              return\n                TextField(\n                  controller: controller,\n                  keyboardType: TextInputType.number,\n                  onChanged: (value) => context.read<AccountAdjustBalanceBloc>().add(AccountAdjustBalanceBalanceChanged(value)),\n                  decoration: InputDecoration(\n                    hintText: '必填项',\n                  )\n                );\n            }\n          )),\n          Text('当前余额：${removeDecimalZero(currentBalance)}', style: Theme.of(context).textTheme.bodyText2),\n        ]\n      )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/adjust_balance/date_time_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/accounts/accounts.dart';\n\nclass AdjustBlanceDateTimeInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocSelector<AccountAdjustBalanceBloc, AccountAdjustBalanceState, int?>(\n      selector: (state) => state.request.createTime,\n      builder: (context, state) {\n        return DateTimeInput(\n          initialTime: state,\n          onDateChange: (value) {\n            BlocProvider.of<AccountAdjustBalanceBloc>(context).add(AccountAdjustBalanceCreateDateChanged(value));\n          },\n          onTimeChange: (value) {\n            BlocProvider.of<AccountAdjustBalanceBloc>(context).add(AccountAdjustBalanceCreateTimeChanged(value));\n          },\n        );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/adjust_balance/description_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass DescriptionInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '描述',\n        BlocSelector<AccountAdjustBalanceBloc, AccountAdjustBalanceState, String?>(\n          selector: (state) => state.request.description,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AccountAdjustBalanceBloc>().add(AccountAdjustBalanceDescriptionChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/adjust_balance/notes_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\n\nclass NoteInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '备注',\n        BlocSelector<AccountAdjustBalanceBloc, AccountAdjustBalanceState, String?>(\n          selector: (state) => state.request.notes,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AccountAdjustBalanceBloc>().add(AccountAdjustBalanceNotesChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/accounts/ui/widgets/order_button.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/accounts/accounts.dart';\n\nclass OrderButton extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocSelector<AccountsBloc, AccountsState, String>(\n        selector: (state) => state.request.sort,\n        builder: (context, state) {\n          return PopupMenu(\n            onSelected: (selected) {\n              if (selected != state) {\n                context.read<AccountsBloc>().add(AccountsSortChanged(selected));\n                context.read<AccountsBloc>().add(AccountsRefreshed());\n              }\n            },\n            items: {\n              'balance,desc': '按余额排序',\n              'enable,desc': '可用优先',\n              'expenseable,desc': '支出优先',\n              'incomeable,desc': '收入优先',\n            },\n            selected: state,\n          );\n        }\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/add_flow.dart",
    "content": "export 'bloc/add_expense/add_expense_bloc.dart';\nexport 'bloc/add_income/add_income_bloc.dart';\nexport 'bloc/add_transfer/add_transfer_bloc.dart';\nexport 'bloc/models/deal_add_request.dart';\nexport 'bloc/models/transfer_add_request.dart';\nexport 'ui/add_flow_page.dart';\nexport 'ui/tab_page.dart';\nexport 'ui/no_tab_page.dart';\nexport 'ui/widgets/expense/add_expense_form.dart';\nexport 'ui/widgets/income/add_income_form.dart';\nexport 'ui/widgets/transfer/add_transfer_form.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_expense/add_expense_bloc.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport '/login/login.dart';\nimport '/flows/flows.dart';\nimport '/add_flow/add_flow.dart';\nimport '/accounts/accounts.dart';\n\npart 'add_expense_event.dart';\npart 'add_expense_state.dart';\n\nclass AddExpenseBloc extends Bloc<AddExpenseEvent, AddExpenseState> {\n\n  AddExpenseBloc({\n    required this.flowRepository,\n    required this.authBloc,\n    required this.accountExpenseableBloc,\n  }) : super(const AddExpenseState()) {\n    on<AddExpenseDefaultLoaded>(_onDefaultLoaded);\n    on<AddExpenseAccountChanged>(_onAccountChanged);\n    on<AddExpensePayeeChanged>(_onPayeeChanged);\n    on<AddExpenseTagChanged>(_onTagChanged);\n    on<AddExpenseDescriptionChanged>(_onDescriptionChanged);\n    on<AddExpenseNotesChanged>(_onNotesChanged);\n    on<AddExpenseCreateTimeChanged>(_onCreateTimeChanged);\n    on<AddExpenseCreateDateChanged>(_onCreateDateChanged);\n    on<AddExpenseCategoryChanged>(_onCategoryChanged);\n    on<AddExpenseAmountChanged>(_onAmountChanged);\n    on<AddExpenseConvertedAmountChanged>(_onConvertedAmountChanged);\n    on<AddExpenseConfirmedChanged>(_onConfirmedChanged);\n    on<AddExpenseSubmitted>(_onSubmitted);\n  }\n\n  final FlowRepository flowRepository;\n  final AuthBloc authBloc;\n  final AccountExpenseableBloc accountExpenseableBloc;\n\n  void _onAccountChanged(AddExpenseAccountChanged event, Emitter<AddExpenseState> emit) {\n    String? currencyCode = null;\n    if (accountExpenseableBloc.state is AccountExpenseableStateLoadSuccess) {\n      AccountExpenseableStateLoadSuccess state = accountExpenseableBloc.state as AccountExpenseableStateLoadSuccess;\n      Account account = state.accounts.firstWhere((account) => account.id.toString() == event.accountId);\n      currencyCode = account.currencyCode;\n    }\n    emit(state.copyWith(\n      request: state.request.copyWith(accountId: event.accountId),\n      currencyCode: currencyCode\n    ));\n  }\n\n  void _onTagChanged(AddExpenseTagChanged event, Emitter<AddExpenseState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(tags: event.tags),\n    ));\n  }\n\n  void _onDescriptionChanged(AddExpenseDescriptionChanged event, Emitter<AddExpenseState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(description: event.description),\n    ));\n  }\n\n  void _onNotesChanged(AddExpenseNotesChanged event, Emitter<AddExpenseState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(notes: event.notes),\n    ));\n  }\n\n  void _onConfirmedChanged(AddExpenseConfirmedChanged event, Emitter<AddExpenseState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(confirmed: event.confirmed),\n    ));\n  }\n\n  void _onPayeeChanged(AddExpensePayeeChanged event, Emitter<AddExpenseState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(payeeId: event.payeeId),\n    ));\n  }\n\n  void _onCreateTimeChanged(AddExpenseCreateTimeChanged event, Emitter<AddExpenseState> emit) {\n    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(state.request.createTime ?? DateTime.now().millisecondsSinceEpoch);\n    dateTime = DateTime(dateTime.year, dateTime.month, dateTime.day, event.time.hour, event.time.minute);\n    emit(state.copyWith(\n      request: state.request.copyWith(createTime: dateTime.millisecondsSinceEpoch),\n    ));\n  }\n\n  void _onCreateDateChanged(AddExpenseCreateDateChanged event, Emitter<AddExpenseState> emit) {\n    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(state.request.createTime ?? DateTime.now().millisecondsSinceEpoch);\n    dateTime = DateTime(event.dateTime.year, event.dateTime.month, event.dateTime.day, dateTime.hour, dateTime.minute);\n    emit(state.copyWith(\n      request: state.request.copyWith(createTime: dateTime.millisecondsSinceEpoch),\n    ));\n  }\n\n  void _onCategoryChanged(AddExpenseCategoryChanged event, Emitter<AddExpenseState> emit) {\n    final List fixedList = Iterable<int>.generate(event.categoryIds.length).toList();\n    emit(state.copyWith(\n      request: state.request.copyWith(\n        categories: fixedList.map((i) {\n          return new CategoryIdAmountRequest(amount: 0, convertedAmount: null, categoryId: event.categoryIds[i], categoryName: event.categoryNames[i]);\n        }).toList(),\n      ),\n    ));\n  }\n\n  void _onAmountChanged(AddExpenseAmountChanged event, Emitter<AddExpenseState> emit) {\n    state.request.categories?.firstWhere((item) => item.categoryId == event.categoryId).amount = num.tryParse(event.amount) ?? 0;\n  }\n\n  void _onConvertedAmountChanged(AddExpenseConvertedAmountChanged event, Emitter<AddExpenseState> emit) {\n    state.request.categories?.firstWhere((item) => item.categoryId == event.categoryId).convertedAmount = num.tryParse(event.convertedAmount) ?? null;\n  }\n\n  void _onDefaultLoaded(AddExpenseDefaultLoaded event, Emitter<AddExpenseState> emit) {\n    List<CategoryIdAmountRequest> defaultCategories = [];\n    if (event.deal != null) {\n      defaultCategories = event.deal!.categories.map((e) =>\n        new CategoryIdAmountRequest(\n          categoryId: e.categoryId.toString(),\n          categoryName: e.categoryName,\n          amount: event.type == 4 ? e.amount*-1 : e.amount,\n          convertedAmount: event.type == 4 ? e.convertedAmount*-1 : e.convertedAmount\n        )\n      ).toList();\n    } else if (authBloc.state.session!.defaultBook.defaultExpenseCategory != null) {\n      defaultCategories = [\n        new CategoryIdAmountRequest(\n          categoryId: authBloc.state.session!.defaultBook.defaultExpenseCategory!.id.toString(),\n          categoryName: authBloc.state.session!.defaultBook.defaultExpenseCategory!.name,\n          amount: 0,\n          convertedAmount: null\n        )\n      ];\n    }\n    String? accountId = event.deal?.account?.id.toString() ?? authBloc.state.session!.defaultBook.defaultExpenseAccount?.id.toString() ?? null;\n    String? currencyCode = null;\n    if (accountExpenseableBloc.state is AccountExpenseableStateLoadSuccess && accountId != null) {\n      AccountExpenseableStateLoadSuccess state = accountExpenseableBloc.state as AccountExpenseableStateLoadSuccess;\n      Account account = state.accounts.firstWhere((account) => account.id.toString() == accountId);\n      currencyCode = account.currencyCode;\n    }\n    emit(state.copyWith(\n      status: FormzStatus.pure,\n      request: state.request.copyWith(\n        description: event.deal?.description ?? '',\n        accountId: accountId,\n        payeeId: event.deal?.payee?.id.toString() ?? '',\n        categories: defaultCategories,\n        tags: event.deal?.tags?.map((e) => e.tagId.toString()).toList() ?? [],\n        createTime: event.type != 2 ? DateTime.now().millisecondsSinceEpoch : event.deal!.createTime,\n        notes: event.type == 2 ? event.deal?.notes ?? '' : '',\n      ),\n      currencyCode: currencyCode\n    ));\n  }\n\n  void _onSubmitted(AddExpenseSubmitted event, Emitter<AddExpenseState> emit) async {\n    try {\n      bool result = false;\n      // 1-新增，2-修改，3-复制，4-退款\n      switch (event.type) {\n        case 1:\n        case 3:\n          result = await flowRepository.saveExpense(state.request);\n          break;\n        case 2:\n          result = await flowRepository.updateExpense(event.deal!.id, state.request);\n          break;\n        case 4:\n          result = await flowRepository.refundExpense(event.deal!.id, state.request);\n          break;\n        default:\n          break;\n      }\n      if (result) {\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n      } else {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: FormzStatus.submissionFailure));\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_expense/add_expense_event.dart",
    "content": "part of 'add_expense_bloc.dart';\n\nabstract class AddExpenseEvent extends Equatable {\n  const AddExpenseEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass AddExpenseCreateTimeChanged extends AddExpenseEvent {\n  const AddExpenseCreateTimeChanged(this.time);\n  final TimeOfDay time;\n  @override\n  List<Object> get props => [time];\n}\n\nclass AddExpenseCreateDateChanged extends AddExpenseEvent {\n  const AddExpenseCreateDateChanged(this.dateTime);\n  final DateTime dateTime;\n  @override\n  List<Object> get props => [dateTime];\n}\n\nclass AddExpenseAccountChanged extends AddExpenseEvent {\n  const AddExpenseAccountChanged(this.accountId);\n  final String accountId;\n  @override\n  List<Object> get props => [accountId];\n}\n\nclass AddExpensePayeeChanged extends AddExpenseEvent {\n  const AddExpensePayeeChanged(this.payeeId);\n  final String payeeId;\n  @override\n  List<Object> get props => [payeeId];\n}\n\nclass AddExpenseCategoryChanged extends AddExpenseEvent {\n  const AddExpenseCategoryChanged(this.categoryIds, this.categoryNames);\n  final List<String> categoryIds;\n  final List<String> categoryNames;\n  @override\n  List<Object> get props => [categoryIds, categoryNames];\n}\n\nclass AddExpenseTagChanged extends AddExpenseEvent {\n  const AddExpenseTagChanged(this.tags);\n  final List<String> tags;\n  @override\n  List<Object> get props => [tags];\n}\n\nclass AddExpenseAmountChanged extends AddExpenseEvent {\n  const AddExpenseAmountChanged(this.categoryId, this.amount);\n  final String categoryId;\n  final String amount;\n  @override\n  List<Object> get props => [categoryId, amount];\n}\n\nclass AddExpenseConvertedAmountChanged extends AddExpenseEvent {\n  const AddExpenseConvertedAmountChanged(this.categoryId, this.convertedAmount);\n  final String categoryId;\n  final String convertedAmount;\n  @override\n  List<Object> get props => [categoryId, convertedAmount];\n}\n\nclass AddExpenseDescriptionChanged extends AddExpenseEvent {\n  const AddExpenseDescriptionChanged(this.description);\n  final String description;\n  @override\n  List<Object> get props => [description];\n}\n\nclass AddExpenseNotesChanged extends AddExpenseEvent {\n  const AddExpenseNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass AddExpenseConfirmedChanged extends AddExpenseEvent {\n  const AddExpenseConfirmedChanged(this.confirmed);\n  final bool confirmed;\n  @override\n  List<Object> get props => [confirmed];\n}\n\nclass AddExpenseDefaultLoaded extends AddExpenseEvent {\n  final int type;\n  final Deal? deal;\n  const AddExpenseDefaultLoaded(this.type, this.deal);\n  @override\n  List<Object?> get props => [type, deal];\n}\n\nclass AddExpenseSubmitted extends AddExpenseEvent {\n  final int type;\n  final Deal? deal;\n  const AddExpenseSubmitted(this.type, this.deal);\n  @override\n  List<Object?> get props => [type, deal];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_expense/add_expense_state.dart",
    "content": "part of 'add_expense_bloc.dart';\n\nclass AddExpenseState extends Equatable {\n\n  final FormzStatus status;\n  final DealAddRequest request;\n  final String? currencyCode;\n\n  const AddExpenseState({\n    this.status = FormzStatus.pure,\n    this.request = const DealAddRequest(),\n    this.currencyCode\n  });\n\n  AddExpenseState copyWith({\n    FormzStatus? status,\n    DealAddRequest? request,\n    String? currencyCode,\n  }) {\n    return AddExpenseState(\n      status: status ?? this.status,\n      request: request ?? this.request,\n      currencyCode: currencyCode ?? this.currencyCode\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, request, currencyCode];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_income/add_income_bloc.dart",
    "content": "import 'package:bookkeeping_user_flutter/accounts/accounts.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport '/login/login.dart';\nimport '/flows/flows.dart';\nimport '../models/deal_add_request.dart';\n\npart 'add_income_event.dart';\npart 'add_income_state.dart';\n\nclass AddIncomeBloc extends Bloc<AddIncomeEvent, AddIncomeState> {\n\n  AddIncomeBloc({\n    required this.flowRepository,\n    required this.authBloc,\n    required this.accountIncomeableBloc\n  }) : super(const AddIncomeState()) {\n    on<AddIncomeDefaultLoaded>(_onDefaultLoaded);\n    on<AddIncomeAccountChanged>(_onAccountChanged);\n    on<AddIncomePayeeChanged>(_onPayeeChanged);\n    on<AddIncomeTagChanged>(_onTagChanged);\n    on<AddIncomeDescriptionChanged>(_onDescriptionChanged);\n    on<AddIncomeNotesChanged>(_onNotesChanged);\n    on<AddIncomeCreateTimeChanged>(_onCreateTimeChanged);\n    on<AddIncomeCreateDateChanged>(_onCreateDateChanged);\n    on<AddIncomeCategoryChanged>(_onCategoryChanged);\n    on<AddIncomeAmountChanged>(_onAmountChanged);\n    on<AddIncomeConvertedAmountChanged>(_onConvertedAmountChanged);\n    on<AddIncomeConfirmedChanged>(_onConfirmedChanged);\n    on<AddIncomeSubmitted>(_onSubmitted);\n  }\n\n  final FlowRepository flowRepository;\n  final AuthBloc authBloc;\n  final AccountIncomeableBloc accountIncomeableBloc;\n\n  void _onAccountChanged(AddIncomeAccountChanged event, Emitter<AddIncomeState> emit) {\n    String? currencyCode = null;\n    if (accountIncomeableBloc.state is AccountIncomeableStateLoadSuccess) {\n      AccountIncomeableStateLoadSuccess state = accountIncomeableBloc.state as AccountIncomeableStateLoadSuccess;\n      Account account = state.accounts.firstWhere((account) => account.id.toString() == event.accountId);\n      currencyCode = account.currencyCode;\n    }\n    emit(state.copyWith(\n        request: state.request.copyWith(accountId: event.accountId),\n        currencyCode: currencyCode\n    ));\n  }\n\n  void _onTagChanged(AddIncomeTagChanged event, Emitter<AddIncomeState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(tags: event.tags),\n    ));\n  }\n\n  void _onDescriptionChanged(AddIncomeDescriptionChanged event, Emitter<AddIncomeState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(description: event.description),\n    ));\n  }\n\n  void _onNotesChanged(AddIncomeNotesChanged event, Emitter<AddIncomeState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(notes: event.notes),\n    ));\n  }\n\n  void _onConfirmedChanged(AddIncomeConfirmedChanged event, Emitter<AddIncomeState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(confirmed: event.confirmed),\n    ));\n  }\n\n  void _onPayeeChanged(AddIncomePayeeChanged event, Emitter<AddIncomeState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(payeeId: event.payeeId),\n    ));\n  }\n\n  void _onCreateTimeChanged(AddIncomeCreateTimeChanged event, Emitter<AddIncomeState> emit) {\n    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(state.request.createTime ?? DateTime.now().millisecondsSinceEpoch);\n    dateTime = new DateTime(dateTime.year, dateTime.month, dateTime.day, event.time.hour, event.time.minute);\n    emit(state.copyWith(\n      request: state.request.copyWith(createTime: dateTime.millisecondsSinceEpoch),\n    ));\n  }\n\n  void _onCreateDateChanged(AddIncomeCreateDateChanged event, Emitter<AddIncomeState> emit) {\n    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(state.request.createTime ?? DateTime.now().millisecondsSinceEpoch);\n    dateTime = new DateTime(event.dateTime.year, event.dateTime.month, event.dateTime.day, dateTime.hour, dateTime.minute);\n    emit(state.copyWith(\n      request: state.request.copyWith(createTime: dateTime.millisecondsSinceEpoch),\n    ));\n  }\n\n  void _onCategoryChanged(AddIncomeCategoryChanged event, Emitter<AddIncomeState> emit) {\n    final List fixedList = Iterable<int>.generate(event.categoryIds.length).toList();\n    emit(state.copyWith(\n      request: state.request.copyWith(\n        categories: fixedList.map((i) {\n          return new CategoryIdAmountRequest(amount: 0, convertedAmount: null, categoryId: event.categoryIds[i], categoryName: event.categoryNames[i]);\n        }).toList(),\n      ),\n    ));\n  }\n\n  void _onAmountChanged(AddIncomeAmountChanged event, Emitter<AddIncomeState> emit) {\n    state.request.categories?.firstWhere((item) => item.categoryId == event.categoryId).amount = num.tryParse(event.amount) ?? 0;\n  }\n\n  void _onConvertedAmountChanged(AddIncomeConvertedAmountChanged event, Emitter<AddIncomeState> emit) {\n    state.request.categories?.firstWhere((item) => item.categoryId == event.categoryId).convertedAmount = num.tryParse(event.convertedAmount) ?? null;\n  }\n\n  void _onDefaultLoaded(AddIncomeDefaultLoaded event, Emitter<AddIncomeState> emit) {\n    List<CategoryIdAmountRequest> defaultCategories = [];\n    if (event.deal != null) {\n      defaultCategories = event.deal!.categories.map((e) =>\n      new CategoryIdAmountRequest(\n        categoryId: e.categoryId.toString(),\n        categoryName: e.categoryName,\n        amount: event.type == 4 ? e.amount*-1 : e.amount,\n        convertedAmount: event.type == 4 ? e.convertedAmount*-1 : e.convertedAmount\n      )\n      ).toList();\n    } else if (authBloc.state.session!.defaultBook.defaultIncomeCategory != null) {\n      defaultCategories = [\n        new CategoryIdAmountRequest(\n          categoryId: authBloc.state.session!.defaultBook.defaultIncomeCategory!.id.toString(),\n          categoryName: authBloc.state.session!.defaultBook.defaultIncomeCategory!.name,\n          amount: 0,\n          convertedAmount: null\n        )\n      ];\n    }\n    String? accountId = event.deal?.account?.id.toString() ?? authBloc.state.session!.defaultBook.defaultIncomeAccount?.id.toString() ?? null;\n    String? currencyCode = null;\n    if (accountIncomeableBloc.state is AccountIncomeableStateLoadSuccess && accountId != null) {\n      AccountIncomeableStateLoadSuccess state = accountIncomeableBloc.state as AccountIncomeableStateLoadSuccess;\n      Account account = state.accounts.firstWhere((account) => account.id.toString() == accountId);\n      currencyCode = account.currencyCode;\n    }\n    emit(state.copyWith(\n      status: FormzStatus.pure,\n      request: state.request.copyWith(\n        description: event.deal?.description ?? '',\n        accountId: event.deal?.account?.id.toString() ?? authBloc.state.session!.defaultBook.defaultIncomeAccount?.id.toString() ?? '',\n        payeeId: event.deal?.payee?.id.toString() ?? '',\n        categories: defaultCategories,\n        tags: event.deal?.tags?.map((e) => e.tagId.toString()).toList() ?? [],\n        createTime: event.type != 2 ? DateTime.now().millisecondsSinceEpoch : event.deal!.createTime,\n        notes: event.type == 2 ? event.deal?.notes ?? '' : '',\n      ),\n      currencyCode: currencyCode\n    ));\n  }\n\n  void _onSubmitted(AddIncomeSubmitted event, Emitter<AddIncomeState> emit) async {\n    try {\n      bool result = false;\n      // 1-新增，2-修改，3-复制，4-退款\n      switch (event.type) {\n        case 1:\n        case 3:\n          result = await flowRepository.saveIncome(state.request);\n          break;\n        case 2:\n          result = await flowRepository.updateIncome(event.deal!.id, state.request);\n          break;\n        case 4:\n          result = await flowRepository.refundIncome(event.deal!.id, state.request);\n          break;\n        default:\n          break;\n      }\n      if (result) {\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n      } else {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: FormzStatus.submissionFailure));\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_income/add_income_event.dart",
    "content": "part of 'add_income_bloc.dart';\n\nabstract class AddIncomeEvent extends Equatable {\n  const AddIncomeEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass AddIncomeCreateTimeChanged extends AddIncomeEvent {\n  const AddIncomeCreateTimeChanged(this.time);\n  final TimeOfDay time;\n  @override\n  List<Object> get props => [time];\n}\n\nclass AddIncomeCreateDateChanged extends AddIncomeEvent {\n  const AddIncomeCreateDateChanged(this.dateTime);\n  final DateTime dateTime;\n  @override\n  List<Object> get props => [dateTime];\n}\n\nclass AddIncomeAccountChanged extends AddIncomeEvent {\n  const AddIncomeAccountChanged(this.accountId);\n  final String accountId;\n  @override\n  List<Object> get props => [accountId];\n}\n\nclass AddIncomePayeeChanged extends AddIncomeEvent {\n  const AddIncomePayeeChanged(this.payeeId);\n  final String payeeId;\n  @override\n  List<Object> get props => [payeeId];\n}\n\nclass AddIncomeCategoryChanged extends AddIncomeEvent {\n  const AddIncomeCategoryChanged(this.categoryIds, this.categoryNames);\n  final List<String> categoryIds;\n  final List<String> categoryNames;\n  @override\n  List<Object> get props => [categoryIds, categoryNames];\n}\n\nclass AddIncomeTagChanged extends AddIncomeEvent {\n  const AddIncomeTagChanged(this.tags);\n  final List<String> tags;\n  @override\n  List<Object> get props => [tags];\n}\n\nclass AddIncomeAmountChanged extends AddIncomeEvent {\n  const AddIncomeAmountChanged(this.categoryId, this.amount);\n  final String categoryId;\n  final String amount;\n  @override\n  List<Object> get props => [categoryId, amount];\n}\n\nclass AddIncomeConvertedAmountChanged extends AddIncomeEvent {\n  const AddIncomeConvertedAmountChanged(this.categoryId, this.convertedAmount);\n  final String categoryId;\n  final String convertedAmount;\n  @override\n  List<Object> get props => [categoryId, convertedAmount];\n}\n\nclass AddIncomeDescriptionChanged extends AddIncomeEvent {\n  const AddIncomeDescriptionChanged(this.description);\n  final String description;\n  @override\n  List<Object> get props => [description];\n}\n\nclass AddIncomeNotesChanged extends AddIncomeEvent {\n  const AddIncomeNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass AddIncomeConfirmedChanged extends AddIncomeEvent {\n  const AddIncomeConfirmedChanged(this.confirmed);\n  final bool confirmed;\n  @override\n  List<Object> get props => [confirmed];\n}\n\nclass AddIncomeDefaultLoaded extends AddIncomeEvent {\n  final int type;\n  final Deal? deal;\n  const AddIncomeDefaultLoaded(this.type, this.deal);\n  @override\n  List<Object?> get props => [type, deal];\n}\n\nclass AddIncomeSubmitted extends AddIncomeEvent {\n  final int type;\n  final Deal? deal;\n  const AddIncomeSubmitted(this.type, this.deal);\n  @override\n  List<Object?> get props => [type, deal];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_income/add_income_state.dart",
    "content": "part of 'add_income_bloc.dart';\n\nclass AddIncomeState extends Equatable {\n\n  final FormzStatus status;\n  final DealAddRequest request;\n  final String? currencyCode;\n\n  const AddIncomeState({\n    this.status = FormzStatus.pure,\n    this.request = const DealAddRequest(),\n    this.currencyCode\n  });\n\n  AddIncomeState copyWith({\n    FormzStatus? status,\n    DealAddRequest? request,\n    String? currencyCode,\n  }) {\n    return AddIncomeState(\n      status: status ?? this.status,\n      request: request ?? this.request,\n      currencyCode: currencyCode ?? this.currencyCode\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, request, currencyCode];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_transfer/add_transfer_bloc.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport '/login/login.dart';\nimport '/flows/flows.dart';\nimport '/add_flow/add_flow.dart';\nimport '/accounts/accounts.dart';\n\npart 'add_transfer_event.dart';\npart 'add_transfer_state.dart';\n\nclass AddTransferBloc extends Bloc<AddTransferEvent, AddTransferState> {\n\n  AddTransferBloc({\n    required this.flowRepository,\n    required this.authBloc,\n    required this.accountTransferFromAbleBloc,\n    required this.accountTransferToAbleBloc,\n  }) : super(const AddTransferState()) {\n    on<AddTransferDefaultLoaded>(_onDefaultLoaded);\n    on<AddTransferFromChanged>(_onFromChanged);\n    on<AddTransferToChanged>(_onToChanged);\n    on<AddTransferAmountChanged>(_onAmountChanged);\n    on<AddTransferConvertedAmountChanged>(_onConvertedAmountChanged);\n    on<AddTransferTagChanged>(_onTagChanged);\n    on<AddTransferDescriptionChanged>(_onDescriptionChanged);\n    on<AddTransferNotesChanged>(_onNotesChanged);\n    on<AddTransferCreateTimeChanged>(_onCreateTimeChanged);\n    on<AddTransferCreateDateChanged>(_onCreateDateChanged);\n    on<AddTransferConfirmedChanged>(_onConfirmedChanged);\n    on<AddTransferSubmitted>(_onSubmitted);\n  }\n\n  final FlowRepository flowRepository;\n  final AuthBloc authBloc;\n  final AccountTransferFromAbleBloc accountTransferFromAbleBloc;\n  final AccountTransferToAbleBloc accountTransferToAbleBloc;\n\n  void _onFromChanged(AddTransferFromChanged event, Emitter<AddTransferState> emit) {\n    String? fromCurrencyCode = null;\n    if (accountTransferFromAbleBloc.state is AccountTransferFromAbleStateLoadSuccess) {\n      AccountTransferFromAbleStateLoadSuccess state = accountTransferFromAbleBloc.state as AccountTransferFromAbleStateLoadSuccess;\n      Account account = state.accounts.firstWhere((account) => account.id.toString() == event.fromId);\n      fromCurrencyCode = account.currencyCode;\n    }\n    emit(state.copyWith(\n      request: state.request.copyWith(fromId: event.fromId),\n      fromCurrencyCode: fromCurrencyCode\n    ));\n  }\n\n  void _onToChanged(AddTransferToChanged event, Emitter<AddTransferState> emit) {\n    String? toCurrencyCode = null;\n    if (accountTransferToAbleBloc.state is AccountTransferToAbleStateLoadSuccess) {\n      AccountTransferToAbleStateLoadSuccess state = accountTransferToAbleBloc.state as AccountTransferToAbleStateLoadSuccess;\n      Account account = state.accounts.firstWhere((account) => account.id.toString() == event.toId);\n      toCurrencyCode = account.currencyCode;\n    }\n    emit(state.copyWith(\n      request: state.request.copyWith(toId: event.toId),\n      toCurrencyCode: toCurrencyCode\n    ));\n  }\n\n  void _onTagChanged(AddTransferTagChanged event, Emitter<AddTransferState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(tags: event.tags),\n    ));\n  }\n\n  void _onDescriptionChanged(AddTransferDescriptionChanged event, Emitter<AddTransferState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(description: event.description),\n    ));\n  }\n\n  void _onNotesChanged(AddTransferNotesChanged event, Emitter<AddTransferState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(notes: event.notes),\n    ));\n  }\n\n  void _onConfirmedChanged(AddTransferConfirmedChanged event, Emitter<AddTransferState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(confirmed: event.confirmed),\n    ));\n  }\n\n  void _onCreateTimeChanged(AddTransferCreateTimeChanged event, Emitter<AddTransferState> emit) {\n    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(state.request.createTime ?? DateTime.now().millisecondsSinceEpoch);\n    dateTime = new DateTime(dateTime.year, dateTime.month, dateTime.day, event.time.hour, event.time.minute);\n    emit(state.copyWith(\n      request: state.request.copyWith(createTime: dateTime.millisecondsSinceEpoch),\n    ));\n  }\n\n  void _onCreateDateChanged(AddTransferCreateDateChanged event, Emitter<AddTransferState> emit) {\n    DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(state.request.createTime ?? DateTime.now().millisecondsSinceEpoch);\n    dateTime = new DateTime(event.dateTime.year, event.dateTime.month, event.dateTime.day, dateTime.hour, dateTime.minute);\n    emit(state.copyWith(\n      request: state.request.copyWith(createTime: dateTime.millisecondsSinceEpoch),\n    ));\n  }\n\n  void _onAmountChanged(AddTransferAmountChanged event, Emitter<AddTransferState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(amount: num.tryParse(event.amount) ?? 0),\n    ));\n  }\n\n  void _onConvertedAmountChanged(AddTransferConvertedAmountChanged event, Emitter<AddTransferState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(convertedAmount: num.tryParse(event.convertedAmount) ?? null),\n    ));\n  }\n\n  void _onDefaultLoaded(AddTransferDefaultLoaded event, Emitter<AddTransferState> emit) {\n    String? fromId = event.transfer?.from.id.toString() ?? authBloc.state.session!.defaultBook.defaultTransferFromAccount?.id.toString() ?? null;\n    String? fromCurrencyCode = null;\n    if (accountTransferFromAbleBloc.state is AccountTransferFromAbleStateLoadSuccess && fromId != null) {\n      AccountTransferFromAbleStateLoadSuccess state = accountTransferFromAbleBloc.state as AccountTransferFromAbleStateLoadSuccess;\n      Account account = state.accounts.firstWhere((account) => account.id.toString() == fromId);\n      fromCurrencyCode = account.currencyCode;\n    }\n    String? toId = event.transfer?.to.id.toString() ?? authBloc.state.session!.defaultBook.defaultTransferToAccount?.id.toString() ?? null;\n    String? toCurrencyCode = null;\n    if (accountTransferToAbleBloc.state is AccountTransferToAbleStateLoadSuccess && toId != null) {\n      AccountTransferToAbleStateLoadSuccess state = accountTransferToAbleBloc.state as AccountTransferToAbleStateLoadSuccess;\n      Account account = state.accounts.firstWhere((account) => account.id.toString() == toId);\n      toCurrencyCode = account.currencyCode;\n    }\n    emit(state.copyWith(\n      status: FormzStatus.pure,\n      request: state.request.copyWith(\n        description: event.transfer?.description ?? '',\n        fromId: event.transfer?.from.id.toString() ?? authBloc.state.session!.defaultBook.defaultTransferFromAccount?.id.toString() ?? '',\n        toId: event.transfer?.to.id.toString() ?? authBloc.state.session!.defaultBook.defaultTransferToAccount?.id.toString() ?? '',\n        amount: event.transfer?.amount,\n        convertedAmount: event.transfer?.convertedAmount,\n        tags: event.transfer?.tags?.map((e) => e.tagId.toString()).toList() ?? [],\n        createTime: event.type != 2 ? DateTime.now().millisecondsSinceEpoch : event.transfer!.createTime,\n        notes: event.type == 2 ? event.transfer?.notes ?? '' : '',\n      ),\n      fromCurrencyCode: fromCurrencyCode,\n      toCurrencyCode: toCurrencyCode\n    ));\n  }\n\n  void _onSubmitted(AddTransferSubmitted event, Emitter<AddTransferState> emit) async {\n    try {\n      bool result = false;\n      // 1-新增，2-修改，3-复制，4-退款\n      switch (event.type) {\n        case 1:\n        case 3:\n          result = await flowRepository.saveTransfer(state.request);\n          break;\n        case 2:\n          result = await flowRepository.updateTransfer(event.transfer!.id, state.request);\n          break;\n        default:\n          break;\n      }\n      if (result) {\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n      } else {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: FormzStatus.submissionFailure));\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_transfer/add_transfer_event.dart",
    "content": "part of 'add_transfer_bloc.dart';\n\nabstract class AddTransferEvent extends Equatable {\n  const AddTransferEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass AddTransferCreateTimeChanged extends AddTransferEvent {\n  const AddTransferCreateTimeChanged(this.time);\n  final TimeOfDay time;\n  @override\n  List<Object> get props => [time];\n}\n\nclass AddTransferCreateDateChanged extends AddTransferEvent {\n  const AddTransferCreateDateChanged(this.dateTime);\n  final DateTime dateTime;\n  @override\n  List<Object> get props => [dateTime];\n}\n\nclass AddTransferFromChanged extends AddTransferEvent {\n  const AddTransferFromChanged(this.fromId);\n  final String fromId;\n  @override\n  List<Object> get props => [fromId];\n}\n\nclass AddTransferToChanged extends AddTransferEvent {\n  const AddTransferToChanged(this.toId);\n  final String toId;\n  @override\n  List<Object> get props => [toId];\n}\n\nclass AddTransferTagChanged extends AddTransferEvent {\n  const AddTransferTagChanged(this.tags);\n  final List<String> tags;\n  @override\n  List<Object> get props => [tags];\n}\n\nclass AddTransferAmountChanged extends AddTransferEvent {\n  const AddTransferAmountChanged(this.amount);\n  final String amount;\n  @override\n  List<Object> get props => [amount];\n}\n\nclass AddTransferConvertedAmountChanged extends AddTransferEvent {\n  const AddTransferConvertedAmountChanged(this.convertedAmount);\n  final String convertedAmount;\n  @override\n  List<Object> get props => [convertedAmount];\n}\n\nclass AddTransferDescriptionChanged extends AddTransferEvent {\n  const AddTransferDescriptionChanged(this.description);\n  final String description;\n  @override\n  List<Object> get props => [description];\n}\n\nclass AddTransferNotesChanged extends AddTransferEvent {\n  const AddTransferNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass AddTransferConfirmedChanged extends AddTransferEvent {\n  const AddTransferConfirmedChanged(this.confirmed);\n  final bool confirmed;\n  @override\n  List<Object> get props => [confirmed];\n}\n\nclass AddTransferDefaultLoaded extends AddTransferEvent {\n  final int type;\n  final Transfer? transfer;\n  const AddTransferDefaultLoaded(this.type, this.transfer);\n  @override\n  List<Object?> get props => [type, transfer];\n}\n\nclass AddTransferSubmitted extends AddTransferEvent {\n  final int type;\n  final Transfer? transfer;\n  const AddTransferSubmitted(this.type, this.transfer);\n  @override\n  List<Object?> get props => [type, transfer];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/add_transfer/add_transfer_state.dart",
    "content": "part of 'add_transfer_bloc.dart';\n\nclass AddTransferState extends Equatable {\n\n  final FormzStatus status;\n  final TransferAddRequest request;\n  final String? fromCurrencyCode;\n  final String? toCurrencyCode;\n\n  const AddTransferState({\n    this.status = FormzStatus.pure,\n    this.request = const TransferAddRequest(),\n    this.fromCurrencyCode,\n    this.toCurrencyCode\n  });\n\n  AddTransferState copyWith({\n    FormzStatus? status,\n    TransferAddRequest? request,\n    String? fromCurrencyCode,\n    String? toCurrencyCode\n  }) {\n    return AddTransferState(\n      status: status ?? this.status,\n      request: request ?? this.request,\n      fromCurrencyCode: fromCurrencyCode ?? this.fromCurrencyCode,\n      toCurrencyCode: toCurrencyCode ?? this.toCurrencyCode\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, request, fromCurrencyCode, toCurrencyCode];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/models/deal_add_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\n\npart 'deal_add_request.g.dart';\n\n@JsonSerializable()\nclass DealAddRequest {\n\n  final String? description;\n  final int? createTime;\n  final String? accountId;\n  final List<CategoryIdAmountRequest>? categories;\n  final String? payeeId;\n  final List<String>? tags;\n  final bool? confirmed;\n  final String? notes;\n\n  const DealAddRequest({\n    this.description,\n    this.createTime,\n    this.accountId,\n    this.categories,\n    this.payeeId,\n    this.tags,\n    this.confirmed,\n    this.notes\n  });\n\n  DealAddRequest copyWith({\n    String? description,\n    int? createTime,\n    String? accountId,\n    String? payeeId,\n    List<CategoryIdAmountRequest>? categories,\n    List<String>? tags,\n    bool? confirmed = true,\n    String? notes\n  }) {\n    return DealAddRequest(\n      description: description ?? this.description,\n      createTime: createTime ?? this.createTime,\n      accountId: accountId ?? this.accountId,\n      payeeId: payeeId ?? this.payeeId,\n      categories: categories ?? this.categories,\n      tags: tags ?? this.tags,\n      confirmed: confirmed ?? this.confirmed,\n      notes: notes ?? this.notes\n    );\n  }\n\n  //factory ExpenseAddRequest.fromJson(Map<String, dynamic> json) => _$ExpenseAddRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$DealAddRequestToJson(this);\n\n  List<String> get categoryIds {\n    if (categories != null) {\n      return categories!.map((i) {return i.categoryId;}).toList();\n    } else {\n      return [];\n    }\n  }\n\n}\n\n@JsonSerializable()\nclass CategoryIdAmountRequest {\n\n  String categoryId;\n  String categoryName;\n  num amount;\n  num? convertedAmount;\n\n  CategoryIdAmountRequest({\n    required this.categoryId,\n    required this.categoryName,\n    required this.amount,\n    this.convertedAmount,\n  });\n\n  factory CategoryIdAmountRequest.fromJson(Map<String, dynamic> json) => _$CategoryIdAmountRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$CategoryIdAmountRequestToJson(this);\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/models/deal_add_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'deal_add_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nDealAddRequest _$DealAddRequestFromJson(Map<String, dynamic> json) =>\n    DealAddRequest(\n      description: json['description'] as String?,\n      createTime: json['createTime'] as int?,\n      accountId: json['accountId'] as String?,\n      categories: (json['categories'] as List<dynamic>?)\n          ?.map((e) =>\n              CategoryIdAmountRequest.fromJson(e as Map<String, dynamic>))\n          .toList(),\n      payeeId: json['payeeId'] as String?,\n      tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),\n      confirmed: json['confirmed'] as bool?,\n      notes: json['notes'] as String?,\n    );\n\nMap<String, dynamic> _$DealAddRequestToJson(DealAddRequest instance) =>\n    <String, dynamic>{\n      'description': instance.description,\n      'createTime': instance.createTime,\n      'accountId': instance.accountId,\n      'categories': instance.categories,\n      'payeeId': instance.payeeId,\n      'tags': instance.tags,\n      'confirmed': instance.confirmed,\n      'notes': instance.notes,\n    };\n\nCategoryIdAmountRequest _$CategoryIdAmountRequestFromJson(\n        Map<String, dynamic> json) =>\n    CategoryIdAmountRequest(\n      categoryId: json['categoryId'] as String,\n      categoryName: json['categoryName'] as String,\n      amount: json['amount'] as num,\n      convertedAmount: json['convertedAmount'] as num?,\n    );\n\nMap<String, dynamic> _$CategoryIdAmountRequestToJson(\n        CategoryIdAmountRequest instance) =>\n    <String, dynamic>{\n      'categoryId': instance.categoryId,\n      'categoryName': instance.categoryName,\n      'amount': instance.amount,\n      'convertedAmount': instance.convertedAmount,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/models/transfer_add_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\n\npart 'transfer_add_request.g.dart';\n\n@JsonSerializable()\nclass TransferAddRequest {\n\n  final String? description;\n  final int? createTime;\n  final String? fromId;\n  final String? toId;\n  final num? amount;\n  final num? convertedAmount;\n  final List<String>? tags;\n  final bool? confirmed;\n  final String? notes;\n\n  const TransferAddRequest({\n    this.description,\n    this.createTime,\n    this.fromId,\n    this.toId,\n    this.amount,\n    this.convertedAmount,\n    this.tags,\n    this.confirmed,\n    this.notes\n  });\n\n  TransferAddRequest copyWith({\n    String? description,\n    int? createTime,\n    String? fromId,\n    String? toId,\n    num? amount,\n    num? convertedAmount,\n    List<String>? tags,\n    bool? confirmed = true,\n    String? notes\n  }) {\n    return TransferAddRequest(\n      description: description ?? this.description,\n      createTime: createTime ?? this.createTime,\n      fromId: fromId ?? this.fromId,\n      toId: toId ?? this.toId,\n      amount: amount ?? this.amount,\n      convertedAmount: convertedAmount ?? this.convertedAmount,\n      tags: tags ?? this.tags,\n      confirmed: confirmed ?? this.confirmed,\n      notes: notes ?? this.notes\n    );\n  }\n\n  Map<String, dynamic> toJson() => _$TransferAddRequestToJson(this);\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/bloc/models/transfer_add_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'transfer_add_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nTransferAddRequest _$TransferAddRequestFromJson(Map<String, dynamic> json) =>\n    TransferAddRequest(\n      description: json['description'] as String?,\n      createTime: json['createTime'] as int?,\n      fromId: json['fromId'] as String?,\n      toId: json['toId'] as String?,\n      amount: json['amount'] as num?,\n      convertedAmount: json['convertedAmount'] as num?,\n      tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),\n      confirmed: json['confirmed'] as bool?,\n      notes: json['notes'] as String?,\n    );\n\nMap<String, dynamic> _$TransferAddRequestToJson(TransferAddRequest instance) =>\n    <String, dynamic>{\n      'description': instance.description,\n      'createTime': instance.createTime,\n      'fromId': instance.fromId,\n      'toId': instance.toId,\n      'amount': instance.amount,\n      'convertedAmount': instance.convertedAmount,\n      'tags': instance.tags,\n      'confirmed': instance.confirmed,\n      'notes': instance.notes,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/add_flow_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/login/login.dart';\nimport '/add_flow/add_flow.dart';\nimport '/flows/flows.dart';\nimport '/accounts/accounts.dart';\n\nclass AddFlowPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改，3-复制，4-退款\n  final FlowModel? flow;\n\n  AddFlowPage({\n    required this.type,\n    this.flow,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocProvider(\n      providers: [\n        BlocProvider(\n          create: (_) =>\n            AddExpenseBloc(\n              flowRepository: RepositoryProvider.of<FlowRepository>(context),\n              authBloc: BlocProvider.of<AuthBloc>(context),\n              accountExpenseableBloc: BlocProvider.of<AccountExpenseableBloc>(context),\n            )..add(AddExpenseDefaultLoaded(type, flow?.expense)),\n        ),\n        BlocProvider(\n          create: (_) =>\n          AddIncomeBloc(\n            flowRepository: RepositoryProvider.of<FlowRepository>(context),\n            authBloc: BlocProvider.of<AuthBloc>(context),\n            accountIncomeableBloc: BlocProvider.of<AccountIncomeableBloc>(context),\n          )..add(AddIncomeDefaultLoaded(type, flow?.income)),\n        ),\n        BlocProvider(\n          create: (_) =>\n          AddTransferBloc(\n            flowRepository: RepositoryProvider.of<FlowRepository>(context),\n            authBloc: BlocProvider.of<AuthBloc>(context),\n            accountTransferFromAbleBloc: BlocProvider.of<AccountTransferFromAbleBloc>(context),\n            accountTransferToAbleBloc: BlocProvider.of<AccountTransferToAbleBloc>(context),\n          )..add(AddTransferDefaultLoaded(type, flow?.transfer)),\n        ),\n      ],\n      child: type == 1 ? TabPage() : NoTabPage(type: type, flow: flow!)\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/no_tab_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/accounts/accounts.dart';\nimport '/flows/flows.dart';\nimport '/add_flow/add_flow.dart';\n\nclass NoTabPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改，3-复制，4-退款\n  final FlowModel flow;\n\n  NoTabPage({\n    required this.type,\n    required this.flow\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        centerTitle: true,\n        title: Text(_buildTitle(type, flow)),\n        actions: [\n          IconButton(\n            icon: Icon(Icons.done),\n            onPressed: () {\n              switch (flow.type) {\n                case 1:\n                  BlocProvider.of<AddExpenseBloc>(context).add(AddExpenseSubmitted(type, flow.expense));\n                  break;\n                case 2:\n                  BlocProvider.of<AddIncomeBloc>(context).add(AddIncomeSubmitted(type, flow.income));\n                  break;\n                case 3:\n                  BlocProvider.of<AddTransferBloc>(context).add(AddTransferSubmitted(type, flow.transfer));\n                  break;\n                case 4:\n                  BlocProvider.of<AccountAdjustBalanceBloc>(context).add(AccountAdjustBalanceSubmitted(2, null, flow.adjustBalance));\n                  break;\n                default:\n                  break;\n              }\n            },\n          )\n        ]\n      ),\n      body: _buildBody(flow)\n    );\n  }\n\n  Widget _buildBody(FlowModel flow) {\n    switch (flow.type) {\n      case 1:\n        return AddExpenseForm(type: type);\n      case 2:\n        return AddIncomeForm(type: type);\n      case 3:\n        return AddTransferForm(type: type);\n      case 4:\n        return AdjustBalanceForm(type: type, adjustBalance: flow.adjustBalance);\n      default:\n        return PageError(msg: '账单类型异常');\n    }\n  }\n\n  String _buildTitle(int type, FlowModel flow) {\n    switch (type) {\n      case 2:\n        return '修改${flow.typeName}';\n      case 3:\n        return '复制${flow.typeName}';\n      case 4:\n        return '${flow.typeName}退款';\n      default:\n        return '账单操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/tab_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/add_flow/add_flow.dart';\n\nclass TabPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return DefaultTabController(\n      length: 3,\n      initialIndex: 0,\n      child: Scaffold(\n        appBar: AppBar(\n            title: TabBar(\n              labelPadding: EdgeInsets.all(0),\n              tabs: [\n                Tab(child: Text('支出')),\n                Tab(child: Text('收入')),\n                Tab(child: Text('转账')),\n              ],\n            ),\n            actions: [\n              // 必须加Builder包装，否则DefaultTabController.of(context)找不到。\n              Builder(builder: (context) {\n                return IconButton(\n                  icon: Icon(Icons.done),\n                  onPressed: () {\n                    switch (DefaultTabController.of(context)!.index) {\n                      case 0:\n                        BlocProvider.of<AddExpenseBloc>(context).add(AddExpenseSubmitted(1, null));\n                        break;\n                      case 1:\n                        BlocProvider.of<AddIncomeBloc>(context).add(AddIncomeSubmitted(1, null));\n                        break;\n                      case 2:\n                        BlocProvider.of<AddTransferBloc>(context).add(AddTransferSubmitted(1, null));\n                        break;\n                      default:\n                        break;\n                    }\n                  },\n                );\n              })\n            ]\n        ),\n        body: TabBarView(\n          children: [\n            AddExpenseForm(type: 1),\n            AddIncomeForm(type: 1),\n            AddTransferForm(type: 1),\n          ],\n        ),\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/account_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/add_flow/add_flow.dart';\n\nclass AccountInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<AccountExpenseableBloc>().state;\n    return BlocSelector<AddExpenseBloc, AddExpenseState, String?>(\n      selector: (state) => state.request.accountId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single(\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '账户',\n            selectedValue: state ?? '',\n            onChange: (selected) {\n              context.read<AddExpenseBloc>().add(AddExpenseAccountChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is AccountExpenseableStateLoadSuccess ? modelToChoice(state1.accounts) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is AccountExpenseableStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/add_expense_form.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\n\nimport '/add_flow/add_flow.dart';\nimport '/flows/flows.dart';\nimport '/commons/commons.dart';\nimport 'widgets.dart';\n\nclass AddExpenseForm extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改，3-复制，4-退款\n  AddExpenseForm({\n    required this.type,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<AddExpenseBloc, AddExpenseState>(\n      listenWhen: (previous, current) => previous.status != current.status,\n      listener: (context, state) {\n        if (state.status == FormzStatus.submissionSuccess) {\n          Message.success('操作成功');\n          Navigator.of(context).pop();\n          BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n          // 如果是修改和退款，要刷新账单详情页\n          if (type == 2 || type == 4) {\n            BlocProvider.of<FlowFetchBloc>(context).add(FlowFetched());\n          }\n        }\n      },\n      child: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              DescriptionInput(),\n              SizedBox(height: 10),\n              AddExpenseDateTimeInput(),\n              AccountInput(),\n              CategoryInput(),\n              PayeeInput(),\n              TagInput(),\n              IsConfirmSwitch(),\n              NotesInput(),\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/category_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\nimport '/categories/categories.dart';\nimport '/login/login.dart';\n\nclass CategoryInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final categoryState = context.watch<ExpenseCategorySelectBloc>().state;\n    final authState = context.watch<AuthBloc>().state;\n    final defaultCurrencyCode = authState.session!.defaultGroup.defaultCurrencyCode;\n    return BlocBuilder<AddExpenseBloc, AddExpenseState>(\n      buildWhen: (previous, current) => previous.request.categories != current.request.categories || previous.currencyCode != current.currencyCode,\n      builder: (context, state) {\n        return Column(\n          crossAxisAlignment: CrossAxisAlignment.start,\n          children: [\n            SmartSelect<String>.multiple\n            (\n              key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n              title: '分类',\n              selectedValue: state.request.categoryIds,\n              onChange: (selected) {\n                context.read<AddExpenseBloc>().add(AddExpenseCategoryChanged(selected!.value ?? [], selected.title ?? []));\n              },\n              choiceItems: categoryState is ExpenseCategorySelectStateLoadSuccess ? modelToChoice(categoryState.categories) : [],\n              choiceType: S2ChoiceType.chips,\n              modalFilter: true,\n              modalFilterAuto: true,\n              tileBuilder: (context, state) {\n                return S2Tile.fromState(\n                  state,\n                  isLoading: categoryState is ExpenseCategorySelectStateLoadInProgress,\n                  padding: EdgeInsets.zero,\n                );\n              }\n            ),\n            for ( var i in state.request.categories ?? []) buildAddCategoryItem(i, defaultCurrencyCode, state.currencyCode, context)\n          ],\n        );\n      }\n    );\n  }\n\n  Widget buildAddCategoryItem(CategoryIdAmountRequest item, String defaultCurrencyCode, String? currencyCode, BuildContext context) {\n    return\n      Container(\n        child: Column(\n          children: [\n            Row(\n                mainAxisAlignment: MainAxisAlignment.spaceBetween,\n                crossAxisAlignment: CrossAxisAlignment.center,\n                children: [\n                  Text(item.categoryName, style: Theme.of(context).textTheme.bodyText1),\n                  SizedBox(width: 10),\n                  Expanded(\n                      child: TextField(\n                        controller: TextEditingController(text: item.amount != 0 ? removeDecimalZero(item.amount) : ''),\n                        keyboardType: TextInputType.number,\n                        onChanged: (amount) => context.read<AddExpenseBloc>().add(AddExpenseAmountChanged(item.categoryId, amount)),\n                        decoration: InputDecoration(),\n                      )\n                  )\n                ]\n            ),\n            if (currencyCode != null && defaultCurrencyCode != currencyCode)\n              Row(\n                  mainAxisAlignment: MainAxisAlignment.spaceBetween,\n                  crossAxisAlignment: CrossAxisAlignment.center,\n                  children: [\n                    Text('折合${defaultCurrencyCode}', style: Theme.of(context).textTheme.bodyText1),\n                    SizedBox(width: 10),\n                    Expanded(\n                        child: TextField(\n                          controller: TextEditingController(text: item.convertedAmount != null ? removeDecimalZero(item.convertedAmount!) : null),\n                          keyboardType: TextInputType.number,\n                          onChanged: (amount) => context.read<AddExpenseBloc>().add(AddExpenseConvertedAmountChanged(item.categoryId, amount)),\n                          decoration: InputDecoration(),\n                        )\n                    )\n                  ]\n              )\n          ],\n        )\n      );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/date_time_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/add_flow/add_flow.dart';\n\nclass AddExpenseDateTimeInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocSelector<AddExpenseBloc, AddExpenseState, int?>(\n      selector: (state) => state.request.createTime,\n      builder: (context, state) {\n        return DateTimeInput(\n          initialTime: state,\n          onDateChange: (value) {\n            BlocProvider.of<AddExpenseBloc>(context).add(AddExpenseCreateDateChanged(value));\n          },\n          onTimeChange: (value) {\n            BlocProvider.of<AddExpenseBloc>(context).add(AddExpenseCreateTimeChanged(value));\n          },\n        );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/description_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass DescriptionInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '描述',\n        BlocSelector<AddExpenseBloc, AddExpenseState, String?>(\n          selector: (state) => state.request.description,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AddExpenseBloc>().add(AddExpenseDescriptionChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/is_confirm_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass IsConfirmSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否确认\",\n        BlocSelector<AddExpenseBloc, AddExpenseState, bool?>(\n          selector: (state) => state.request.confirmed,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<AddExpenseBloc>().add(AddExpenseConfirmedChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/notes_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass NotesInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '备注',\n        BlocSelector<AddExpenseBloc, AddExpenseState, String?>(\n          selector: (state) => state.request.notes,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AddExpenseBloc>().add(AddExpenseNotesChanged(value)),\n                decoration: InputDecoration()\n            );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/payee_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\nimport '/payees/payees.dart';\n\nclass PayeeInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<PayeeExpenseableBloc>().state;\n    return BlocSelector<AddExpenseBloc, AddExpenseState, String?>(\n      selector: (state) => state.request.payeeId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n          (\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '对象',\n            selectedValue: state ?? '',\n            onChange: (selected) {\n              context.read<AddExpenseBloc>().add(AddExpensePayeeChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is PayeeExpenseableStateLoadSuccess ? modelToChoice(state1.payees) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto:true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is PayeeExpenseableStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n        }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/tag_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\nimport '/tags/tags.dart';\n\nclass TagInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<TagExpenseableBloc>().state;\n    return BlocSelector<AddExpenseBloc, AddExpenseState, List<String>?>(\n      selector: (state) => state.request.tags,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.multiple(\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '标签',\n            selectedValue: state ?? [],\n            onChange: (selected) {\n              context.read<AddExpenseBloc>().add(AddExpenseTagChanged(selected!.value ?? []));\n            },\n            choiceItems: state1 is TagExpenseableStateLoadSuccess ? modelToChoice(state1.tags) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is TagExpenseableStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/expense/widgets.dart",
    "content": "export 'account_input.dart';\nexport 'description_input.dart';\nexport 'date_time_input.dart';\nexport 'category_input.dart';\nexport 'payee_input.dart';\nexport 'tag_input.dart';\nexport 'is_confirm_input.dart';\nexport 'notes_input.dart';\nexport 'add_expense_form.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/account_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/add_flow/add_flow.dart';\n\nclass AccountInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<AccountIncomeableBloc>().state;\n    return BlocSelector<AddIncomeBloc, AddIncomeState, String?>(\n      selector: (state) => state.request.accountId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single(\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '账户',\n            selectedValue: state ?? '',\n            onChange: (selected) {\n              context.read<AddIncomeBloc>().add(AddIncomeAccountChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is AccountIncomeableStateLoadSuccess ? modelToChoice(state1.accounts) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is AccountIncomeableStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/add_income_form.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\n\nimport '/add_flow/add_flow.dart';\nimport '/flows/flows.dart';\nimport '/commons/commons.dart';\n\nimport 'widgets.dart';\n\nclass AddIncomeForm extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改，3-复制，4-退款\n  AddIncomeForm({\n    required this.type,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocListener<AddIncomeBloc, AddIncomeState>(\n      listenWhen: (previous, current) => previous.status != current.status,\n      listener: (context, state) {\n        if (state.status == FormzStatus.submissionSuccess) {\n          Message.success('操作成功');\n          Navigator.of(context).pop();\n          BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n          // 如果是修改，要刷新账单详情页\n          if (type == 2 || type == 4) {\n            BlocProvider.of<FlowFetchBloc>(context).add(FlowFetched());\n          }\n        }\n      },\n      child: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              DescriptionInput(),\n              SizedBox(height: 10),\n              AddIncomeDateTimeInput(),\n              AccountInput(),\n              CategoryInput(),\n              PayeeInput(),\n              TagInput(),\n              IsConfirmSwitch(),\n              NotesInput(),\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/category_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\nimport '/categories/categories.dart';\nimport '/login/login.dart';\n\nclass CategoryInput extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    final categoryState = context.watch<IncomeCategorySelectBloc>().state;\n    final authState = context.watch<AuthBloc>().state;\n    final defaultCurrencyCode = authState.session!.defaultGroup.defaultCurrencyCode;\n    return BlocBuilder<AddIncomeBloc, AddIncomeState>(\n      buildWhen: (previous, current) => previous.request.categories != current.request.categories || previous.currencyCode != current.currencyCode,\n      builder: (context, state) {\n        return Column(\n          crossAxisAlignment: CrossAxisAlignment.start,\n          children: [\n            SmartSelect<String>.multiple\n            (\n              key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n              title: '分类',\n              selectedValue: state.request.categoryIds,\n              onChange: (selected) {\n                context.read<AddIncomeBloc>().add(AddIncomeCategoryChanged(selected!.value ?? [], selected.title ?? []));\n              },\n              choiceItems: categoryState is IncomeCategorySelectStateLoadSuccess ? modelToChoice(categoryState.categories) : [],\n              choiceType: S2ChoiceType.chips,\n              modalFilter: true,\n              modalFilterAuto: true,\n              tileBuilder: (context, state) {\n                return S2Tile.fromState(\n                  state,\n                  isLoading: categoryState is IncomeCategorySelectStateLoadInProgress,\n                  padding: EdgeInsets.zero,\n                );\n              }\n            ),\n            for ( var i in state.request.categories ?? []) buildAddCategoryItem(i, defaultCurrencyCode, state.currencyCode, context)\n          ],\n        );\n      }\n    );\n  }\n\n  Widget buildAddCategoryItem(CategoryIdAmountRequest item, String defaultCurrencyCode, String? currencyCode, BuildContext context) {\n    return\n      Container(\n          child: Column(\n            children: [\n              Row(\n                  mainAxisAlignment: MainAxisAlignment.spaceBetween,\n                  crossAxisAlignment: CrossAxisAlignment.center,\n                  children: [\n                    Text(item.categoryName, style: Theme.of(context).textTheme.bodyText1),\n                    SizedBox(width: 10),\n                    Expanded(\n                        child: TextField(\n                          controller: TextEditingController(text: item.amount != 0 ? removeDecimalZero(item.amount) : ''),\n                          keyboardType: TextInputType.number,\n                          onChanged: (amount) => context.read<AddIncomeBloc>().add(AddIncomeAmountChanged(item.categoryId, amount)),\n                          decoration: InputDecoration(),\n                        )\n                    )\n                  ]\n              ),\n              if (currencyCode != null && defaultCurrencyCode != currencyCode)\n                Row(\n                    mainAxisAlignment: MainAxisAlignment.spaceBetween,\n                    crossAxisAlignment: CrossAxisAlignment.center,\n                    children: [\n                      Text('折合${defaultCurrencyCode}', style: Theme.of(context).textTheme.bodyText1),\n                      SizedBox(width: 10),\n                      Expanded(\n                          child: TextField(\n                            controller: TextEditingController(text: item.convertedAmount != null ? removeDecimalZero(item.convertedAmount!) : null),\n                            keyboardType: TextInputType.number,\n                            onChanged: (amount) => context.read<AddIncomeBloc>().add(AddIncomeConvertedAmountChanged(item.categoryId, amount)),\n                            decoration: InputDecoration(),\n                          )\n                      )\n                    ]\n                )\n            ],\n          )\n      );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/date_time_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/add_flow/add_flow.dart';\n\nclass AddIncomeDateTimeInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocSelector<AddIncomeBloc, AddIncomeState, int?>(\n      selector: (state) => state.request.createTime,\n      builder: (context, state) {\n        return DateTimeInput(\n          initialTime: state,\n          onDateChange: (value) {\n            BlocProvider.of<AddIncomeBloc>(context).add(AddIncomeCreateDateChanged(value));\n          },\n          onTimeChange: (value) {\n            BlocProvider.of<AddIncomeBloc>(context).add(AddIncomeCreateTimeChanged(value));\n          },\n        );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/description_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass DescriptionInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '描述',\n        BlocSelector<AddIncomeBloc, AddIncomeState, String?>(\n          selector: (state) => state.request.description,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AddIncomeBloc>().add(AddIncomeDescriptionChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/is_confirm_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass IsConfirmSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否确认\",\n        BlocSelector<AddIncomeBloc, AddIncomeState, bool?>(\n          selector: (state) => state.request.confirmed,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<AddIncomeBloc>().add(AddIncomeConfirmedChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/notes_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass NotesInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '备注',\n        BlocSelector<AddIncomeBloc, AddIncomeState, String?>(\n          selector: (state) => state.request.notes,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AddIncomeBloc>().add(AddIncomeNotesChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/payee_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\nimport '/payees/payees.dart';\n\nclass PayeeInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<PayeeIncomeableBloc>().state;\n    return BlocSelector<AddIncomeBloc, AddIncomeState, String?>(\n      selector: (state) => state.request.payeeId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n          (\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '对象',\n            selectedValue: state ?? '',\n            onChange: (selected) {\n              context.read<AddIncomeBloc>().add(AddIncomePayeeChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is PayeeIncomeableStateLoadSuccess ? modelToChoice(state1.payees) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto:true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is PayeeIncomeableStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n        }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/tag_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\nimport '/tags/tags.dart';\n\nclass TagInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<TagIncomeableBloc>().state;\n    return BlocSelector<AddIncomeBloc, AddIncomeState, List<String>?>(\n      selector: (state) => state.request.tags,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.multiple(\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '标签',\n            selectedValue: state ?? [],\n            onChange: (selected) {\n              context.read<AddIncomeBloc>().add(AddIncomeTagChanged(selected!.value ?? []));\n            },\n            choiceItems: state1 is TagIncomeableStateLoadSuccess ? modelToChoice(state1.tags) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is TagIncomeableStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/income/widgets.dart",
    "content": "export 'account_input.dart';\nexport 'description_input.dart';\nexport 'date_time_input.dart';\nexport 'category_input.dart';\nexport 'payee_input.dart';\nexport 'tag_input.dart';\nexport 'is_confirm_input.dart';\nexport 'notes_input.dart';\nexport 'add_income_form.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/add_transfer_form.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\n\nimport '/add_flow/add_flow.dart';\nimport '/flows/flows.dart';\nimport '/commons/commons.dart';\n\nimport 'widgets.dart';\n\nclass AddTransferForm extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改，3-复制，4-退款\n  AddTransferForm({\n    required this.type,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    final state = context.watch<AddTransferBloc>().state;\n    return BlocListener<AddTransferBloc, AddTransferState>(\n      listenWhen: (previous, current) => previous.status != current.status,\n      listener: (context, state) {\n        if (state.status == FormzStatus.submissionSuccess) {\n          Message.success('操作成功');\n          Navigator.of(context).pop();\n          BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n          // 如果是修改，要刷新账单详情页\n          if (type == 2 || type == 4) {\n            BlocProvider.of<FlowFetchBloc>(context).add(FlowFetched());\n          }\n        }\n      },\n      child: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              DescriptionInput(),\n              SizedBox(height: 10),\n              AddTransferDateTimeInput(),\n              FromInput(),\n              ToInput(),\n              AmountInput(),\n              if (state.fromCurrencyCode != null && state.toCurrencyCode != null && state.fromCurrencyCode != state.toCurrencyCode) SizedBox(height: 10),\n              if (state.fromCurrencyCode != null && state.toCurrencyCode != null && state.fromCurrencyCode != state.toCurrencyCode) ConvertedAmountInput(),\n              SizedBox(height: 10),\n              TagInput(),\n              IsConfirmSwitch(),\n              NotesInput(),\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/amount_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass AmountInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '金额',\n        BlocSelector<AddTransferBloc, AddTransferState, num?>(\n          selector: (state) => state.request.amount,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state != null && state != 0 ? removeDecimalZero(state) : '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state.toString().length),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                keyboardType: TextInputType.number,\n                onChanged: (value) => context.read<AddTransferBloc>().add(AddTransferAmountChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/converted_amount_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass ConvertedAmountInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    final state = context.watch<AddTransferBloc>().state;\n    return\n      buildFormItem(\n        '折合${state.toCurrencyCode}',\n        BlocSelector<AddTransferBloc, AddTransferState, num?>(\n          selector: (state) => state.request.convertedAmount,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state != null && state != 0 ? removeDecimalZero(state) : '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state.toString().length),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                keyboardType: TextInputType.number,\n                onChanged: (value) => context.read<AddTransferBloc>().add(AddTransferConvertedAmountChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/date_time_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/add_flow/add_flow.dart';\n\nclass AddTransferDateTimeInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocSelector<AddTransferBloc, AddTransferState, int?>(\n      selector: (state) => state.request.createTime,\n      builder: (context, state) {\n        return DateTimeInput(\n          initialTime: state,\n          onDateChange: (value) {\n            BlocProvider.of<AddTransferBloc>(context).add(AddTransferCreateDateChanged(value));\n          },\n          onTimeChange: (value) {\n            BlocProvider.of<AddTransferBloc>(context).add(AddTransferCreateTimeChanged(value));\n          },\n        );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/description_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass DescriptionInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '描述',\n        BlocSelector<AddTransferBloc, AddTransferState, String?>(\n          selector: (state) => state.request.description,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AddTransferBloc>().add(AddTransferDescriptionChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/from_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/add_flow/add_flow.dart';\n\nclass FromInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<AccountTransferFromAbleBloc>().state;\n    return BlocSelector<AddTransferBloc, AddTransferState, String?>(\n      selector: (state) => state.request.fromId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single(\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '转出账户',\n            selectedValue: state ?? '',\n            onChange: (selected) {\n              context.read<AddTransferBloc>().add(AddTransferFromChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is AccountTransferFromAbleStateLoadSuccess ? modelToChoice(state1.accounts) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is AccountTransferFromAbleStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/is_confirm_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass IsConfirmSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否确认\",\n        BlocSelector<AddTransferBloc, AddTransferState, bool?>(\n          selector: (state) => state.request.confirmed,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<AddTransferBloc>().add(AddTransferConfirmedChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/notes_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\n\nclass NotesInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '备注',\n        BlocSelector<AddTransferBloc, AddTransferState, String?>(\n          selector: (state) => state.request.notes,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<AddTransferBloc>().add(AddTransferNotesChanged(value)),\n                decoration: InputDecoration()\n              );\n          }\n        ), context\n      );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/tag_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\nimport '/tags/tags.dart';\n\nclass TagInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<TagTransferableBloc>().state;\n    return BlocSelector<AddTransferBloc, AddTransferState, List<String>?>(\n      selector: (state) => state.request.tags,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.multiple(\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '标签',\n            selectedValue: state ?? [],\n            onChange: (selected) {\n              context.read<AddTransferBloc>().add(AddTransferTagChanged(selected!.value ?? []));\n            },\n            choiceItems: state1 is TagTransferableStateLoadSuccess ? modelToChoice(state1.tags) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is TagTransferableStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/to_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/add_flow/add_flow.dart';\n\nclass ToInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<AccountTransferToAbleBloc>().state;\n    return BlocSelector<AddTransferBloc, AddTransferState, String?>(\n      selector: (state) => state.request.toId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single(\n            key: Key(new DateTime.now().millisecondsSinceEpoch.toString()),\n            title: '转入账户',\n            selectedValue: state ?? '',\n            onChange: (selected) {\n              context.read<AddTransferBloc>().add(AddTransferToChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is AccountTransferToAbleStateLoadSuccess ? modelToChoice(state1.accounts) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is AccountTransferToAbleStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n          );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/add_flow/ui/widgets/transfer/widgets.dart",
    "content": "export 'description_input.dart';\nexport 'date_time_input.dart';\nexport 'from_input.dart';\nexport 'to_input.dart';\nexport 'amount_input.dart';\nexport 'converted_amount_input.dart';\nexport 'tag_input.dart';\nexport 'is_confirm_input.dart';\nexport 'notes_input.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/app.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_localizations/flutter_localizations.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/categories/categories.dart';\nimport '/flows/flows.dart';\nimport '/payees/payees.dart';\nimport '/tags/tags.dart';\nimport '/accounts/accounts.dart';\nimport '/login/login.dart';\nimport '/books/books.dart';\nimport '/charts/charts.dart';\nimport '/items/items.dart';\nimport '/currency/currency.dart';\nimport 'themes.dart';\nimport 'routes.dart';\n\nclass App extends StatelessWidget {\n\n  const App({\n    Key? key,\n    required this.loginRepository,\n    required this.accountRepository,\n    required this.payeeRepository,\n    required this.tagRepository,\n    required this.categoryRepository,\n    required this.flowRepository,\n    required this.bookRepository,\n    required this.reportRepository,\n    required this.itemRepository,\n    required this.currencyRepository,\n  }) : super(key: key);\n\n  final LoginRepository loginRepository;\n  final AccountRepository accountRepository;\n  final PayeeRepository payeeRepository;\n  final TagRepository tagRepository;\n  final CategoryRepository categoryRepository;\n  final FlowRepository flowRepository;\n  final BookRepository bookRepository;\n  final ReportRepository reportRepository;\n  final ItemRepository itemRepository;\n  final CurrencyRepository currencyRepository;\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiRepositoryProvider(\n      providers: [\n        RepositoryProvider.value(value: loginRepository),\n        RepositoryProvider.value(value: accountRepository),\n        RepositoryProvider.value(value: categoryRepository),\n        RepositoryProvider.value(value: payeeRepository),\n        RepositoryProvider.value(value: tagRepository),\n        RepositoryProvider.value(value: flowRepository),\n        RepositoryProvider.value(value: bookRepository),\n        RepositoryProvider.value(value: reportRepository),\n        RepositoryProvider.value(value: itemRepository),\n        RepositoryProvider.value(value: currencyRepository),\n      ],\n      child: MultiBlocProvider(\n        providers: [\n          BlocProvider(\n            create: (_) => AuthBloc(loginRepository: loginRepository)..add(AppStarted())\n          ),\n          BlocProvider(\n            create: (_) => FlowsBloc(flowRepository: flowRepository)\n          ),\n          BlocProvider(\n            create: (_) => PayeeExpenseableBloc(payeeRepository: payeeRepository)..add(PayeeExpenseableLoaded())\n          ),\n          BlocProvider(\n            create: (_) => PayeeIncomeableBloc(payeeRepository: payeeRepository)..add(PayeeIncomeableLoaded())\n          ),\n          BlocProvider(\n            create: (_) => PayeeEnableBloc(payeeRepository: payeeRepository)..add(PayeeEnableLoaded())\n          ),\n          BlocProvider(\n            create: (_) => TagExpenseableBloc(tagRepository: tagRepository)..add(TagExpenseableLoaded())\n          ),\n          BlocProvider(\n            create: (_) => TagIncomeableBloc(tagRepository: tagRepository)..add(TagIncomeableLoaded())\n          ),\n          BlocProvider(\n            create: (_) => TagTransferableBloc(tagRepository: tagRepository)..add(TagTransferableLoaded())\n          ),\n          BlocProvider(\n            create: (_) => TagEnableBloc(tagRepository: tagRepository)..add(TagEnableLoaded())\n          ),\n          BlocProvider(\n            create: (_) => ExpenseCategorySelectBloc(categoryRepository: categoryRepository)..add(ExpenseCategorySelectLoaded())\n          ),\n          BlocProvider(\n            create: (_) => IncomeCategorySelectBloc(categoryRepository: categoryRepository)..add(IncomeCategorySelectLoaded())\n          ),\n        ],\n        child: Builder(builder: (context) {\n          return MultiBlocProvider(\n            providers: [\n              BlocProvider(\n                create: (_) => AccountExpenseableBloc(accountRepository: accountRepository)..add(AccountExpenseableLoaded())\n              ),\n              BlocProvider(\n                create: (_) => AccountIncomeableBloc(accountRepository: accountRepository)..add(AccountIncomeableLoaded())\n              ),\n              BlocProvider(\n                create: (_) => AccountTransferFromAbleBloc(accountRepository: accountRepository)..add(AccountTransferFromAbleLoaded())\n              ),\n              BlocProvider(\n                create: (_) => AccountTransferToAbleBloc(accountRepository: accountRepository)..add(AccountTransferToAbleLoaded())\n              ),\n              BlocProvider(\n                create: (_) => AccountEnableBloc(accountRepository: accountRepository)..add(AccountEnableLoaded())\n              ),\n              BlocProvider(\n                create: (_) => CategoryTreeBloc(\n                  categoryRepository: categoryRepository,\n                  expenseCategorySelectBloc: BlocProvider.of<ExpenseCategorySelectBloc>(context),\n                  incomeCategorySelectBloc: BlocProvider.of<IncomeCategorySelectBloc>(context),\n                )\n              ),\n              BlocProvider(\n                create: (_) => CategoryFetchBloc(categoryRepository: categoryRepository)\n              ),\n              BlocProvider(\n                create: (_) => TagTreeBloc(\n                  tagRepository: tagRepository,\n                  tagExpenseableBloc: BlocProvider.of<TagExpenseableBloc>(context),\n                  tagIncomeableBloc: BlocProvider.of<TagIncomeableBloc>(context),\n                  tagTransferableBloc: BlocProvider.of<TagTransferableBloc>(context),\n                  tagEnableBloc: BlocProvider.of<TagEnableBloc>(context),\n                )\n              ),\n              BlocProvider(\n                create: (_) => TagFetchBloc(tagRepository: tagRepository)\n              ),\n              BlocProvider(\n                create: (_) => BookFetchBloc(bookRepository: bookRepository)\n              ),\n              BlocProvider(\n                create: (_) => FlowFetchBloc(flowRepository: flowRepository)\n              ),\n              BlocProvider(\n                create: (_) => AccountsBloc(accountRepository: accountRepository)\n              ),\n              BlocProvider(\n                create: (_) => AccountAdjustBalanceBloc(accountRepository: accountRepository)\n              ),\n              BlocProvider(\n                create: (_) => AccountFetchBloc(accountRepository: accountRepository)\n              ),\n              BlocProvider(\n                create: (_) => PayeesBloc(payeeRepository: payeeRepository)\n              ),\n              BlocProvider(\n                create: (_) => PayeeFetchBloc(payeeRepository: payeeRepository)\n              ),\n              BlocProvider(\n                create: (_) => BooksBloc(\n                  bookRepository: bookRepository,\n                  authBloc: BlocProvider.of<AuthBloc>(context),\n                  flowsBloc: BlocProvider.of<FlowsBloc>(context),\n                  expenseCategorySelectBloc: BlocProvider.of<ExpenseCategorySelectBloc>(context),\n                  incomeCategorySelectBloc: BlocProvider.of<IncomeCategorySelectBloc>(context),\n                  tagExpenseableBloc: BlocProvider.of<TagExpenseableBloc>(context),\n                  tagIncomeableBloc: BlocProvider.of<TagIncomeableBloc>(context),\n                  tagTransferableBloc: BlocProvider.of<TagTransferableBloc>(context),\n                  tagEnableBloc: BlocProvider.of<TagEnableBloc>(context),\n                  payeeExpenseableBloc: BlocProvider.of<PayeeExpenseableBloc>(context),\n                  payeeIncomeableBloc: BlocProvider.of<PayeeIncomeableBloc>(context),\n                  payeeEnableBloc: BlocProvider.of<PayeeEnableBloc>(context),\n                )\n              ),\n              BlocProvider(\n                create: (_) => BookFetchBloc(bookRepository: bookRepository)\n              ),\n              BlocProvider(\n                  create: (_) => ReportAssetBloc(reportRepository: reportRepository)..add(ReportAssetLoaded())\n              ),\n              BlocProvider(\n                  create: (_) => ReportDebtBloc(reportRepository: reportRepository)..add(ReportDebtLoaded())\n              ),\n              BlocProvider(\n                create: (_) => ReportExpenseCategoryBloc(reportRepository: reportRepository)..add(ReportExpenseCategoryRefreshed()),\n              ),\n              BlocProvider(\n                create: (_) => ReportIncomeCategoryBloc(reportRepository: reportRepository)..add(ReportIncomeCategoryRefreshed()),\n              ),\n              BlocProvider(\n                create: (_) => ItemsBloc(itemRepository: itemRepository)\n              ),\n              BlocProvider(\n                create: (_) => CurrencyAllBloc(currencyRepository: currencyRepository)..add(CurrencyAllLoaded()),\n              )\n            ],\n          child: AppView()\n        );\n        })\n      )\n    );\n  }\n}\n\nclass AppView extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      localizationsDelegates: [\n        GlobalMaterialLocalizations.delegate,\n        GlobalCupertinoLocalizations.delegate,\n        GlobalWidgetsLocalizations.delegate,\n      ],\n      supportedLocales: [\n        const Locale('zh'),\n      ],\n      locale: const Locale('zh'),\n      title: '九快记账',\n      theme: lightTheme,\n      darkTheme: darkTheme,\n      themeMode: ThemeMode.system,\n      routes: AppRouter.routes,\n      initialRoute: AppRouter.initialRoute,\n      onGenerateRoute: AppRouter.generateRoute,\n      onUnknownRoute: AppRouter.unknownRoute,\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/bloc/book_fetch/book_fetch_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:bookkeeping_user_flutter/books/books.dart';\nimport 'package:bookkeeping_user_flutter/books/data/models/book.dart';\nimport 'package:bookkeeping_user_flutter/commons/enums.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\n\npart 'book_fetch_event.dart';\npart 'book_fetch_state.dart';\n\nclass BookFetchBloc extends Bloc<BookFetchEvent, BookFetchState> {\n\n  final BookRepository bookRepository;\n\n  BookFetchBloc({\n    required this.bookRepository,\n  }) : super(BookFetchState()) {\n    on<BookFetched>(_onFetched);\n    on<BookLoadDefault>(_onDefault);\n  }\n\n  void _onDefault(BookLoadDefault event, Emitter<BookFetchState> emit) {\n    emit(state.copyWith(\n      status: LoadDataStatus.success,\n      book: event.book\n    ));\n  }\n\n  void _onFetched(_, Emitter<BookFetchState> emit) async {\n    try {\n      emit(state.copyWith(status: LoadDataStatus.progress));\n      final book = await bookRepository.get(state.book!.id);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        book: book,\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/bloc/book_fetch/book_fetch_event.dart",
    "content": "part of 'book_fetch_bloc.dart';\n\n@immutable\nclass BookFetchEvent extends Equatable {\n  const BookFetchEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass BookFetched extends BookFetchEvent {}\n\nclass BookLoadDefault extends BookFetchEvent {\n  final Book book;\n  const BookLoadDefault({\n    required this.book,\n  });\n  List<Object> get props => [book];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/bloc/book_fetch/book_fetch_state.dart",
    "content": "part of 'book_fetch_bloc.dart';\n\n@immutable\nclass BookFetchState extends Equatable {\n\n  final LoadDataStatus status;\n  final Book? book;\n\n  const BookFetchState({\n    this.status = LoadDataStatus.initial,\n    this.book,\n  });\n\n  BookFetchState copyWith({\n    LoadDataStatus? status,\n    Book? book,\n  }) {\n    return BookFetchState(\n      status: status ?? this.status,\n      book: book ?? this.book,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, book];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/bloc/books/books_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\nimport '/login/login.dart';\nimport '/books/books.dart';\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\nimport '/categories/categories.dart';\nimport '/payees/payees.dart';\nimport '/tags/tags.dart';\n\npart 'books_event.dart';\npart 'books_state.dart';\n\nclass BooksBloc extends Bloc<BooksEvent, BooksState> {\n\n  final BookRepository bookRepository;\n  final AuthBloc authBloc;\n  final FlowsBloc flowsBloc;\n  final ExpenseCategorySelectBloc expenseCategorySelectBloc;\n  final IncomeCategorySelectBloc incomeCategorySelectBloc;\n  final TagExpenseableBloc tagExpenseableBloc;\n  final TagIncomeableBloc tagIncomeableBloc;\n  final TagTransferableBloc tagTransferableBloc;\n  final TagEnableBloc tagEnableBloc;\n  final PayeeExpenseableBloc payeeExpenseableBloc;\n  final PayeeIncomeableBloc payeeIncomeableBloc;\n  final PayeeEnableBloc payeeEnableBloc;\n\n  BooksBloc({\n    required this.bookRepository,\n    required this.authBloc,\n    required this.flowsBloc,\n    required this.expenseCategorySelectBloc,\n    required this.incomeCategorySelectBloc,\n    required this.tagExpenseableBloc,\n    required this.tagIncomeableBloc,\n    required this.tagTransferableBloc,\n    required this.tagEnableBloc,\n    required this.payeeExpenseableBloc,\n    required this.payeeIncomeableBloc,\n    required this.payeeEnableBloc,\n  }) : super(BooksState()) {\n    on<BooksRefreshed>(_onRefreshed);\n    on<BooksLoadMore>(_onLoadMore);\n    on<BookSetDefault>(_onDefault);\n  }\n\n  void _onRefreshed(_, Emitter<BooksState> emit) async {\n    try {\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n        request: state.request.copyWith(page: 1),\n      ));\n      final books = await bookRepository.query(state.request);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        books: books,\n        request: state.request.copyWith(page: state.request.page + 1),\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onLoadMore(_, Emitter<BooksState> emit) async {\n    try {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.progress));\n      final books = await bookRepository.query(state.request);\n      if (books.isNotEmpty) {\n        emit(state.copyWith(\n          request: state.request.copyWith(page: state.request.page + 1),\n          books: List.of(state.books)..addAll(books),\n          loadMoreStatus: LoadDataStatus.success,\n        ));\n      } else {\n        emit(state.copyWith(loadMoreStatus: LoadDataStatus.empty));\n      }\n    } catch (_) {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onDefault(BookSetDefault event, Emitter<BooksState> emit) async {\n    try {\n      emit(state.copyWith(defaultStatus: LoadDataStatus.progress));\n      final result = await bookRepository.setDefault(event.book.id.toString());\n      if (result) {\n        emit(state.copyWith(defaultStatus: LoadDataStatus.success));\n        authBloc.add(DefaultBookChanged(event.book));\n        flowsBloc.add(FlowsRefreshed());\n        expenseCategorySelectBloc.add(ExpenseCategorySelectLoaded());\n        incomeCategorySelectBloc.add(IncomeCategorySelectLoaded());\n        tagExpenseableBloc.add(TagExpenseableLoaded());\n        tagIncomeableBloc.add(TagIncomeableLoaded());\n        tagTransferableBloc.add(TagTransferableLoaded());\n        tagEnableBloc.add(TagEnableLoaded());\n        payeeExpenseableBloc.add(PayeeExpenseableLoaded());\n        payeeIncomeableBloc.add(PayeeIncomeableLoaded());\n        payeeEnableBloc.add(PayeeEnableLoaded());\n      } else {\n        emit(state.copyWith(defaultStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(defaultStatus: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/bloc/books/books_event.dart",
    "content": "part of 'books_bloc.dart';\n\n@immutable\nabstract class BooksEvent extends Equatable {\n  const BooksEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass BooksRefreshed extends BooksEvent { }\n\nclass BooksLoadMore extends BooksEvent { }\n\nclass BookSetDefault extends BooksEvent {\n  final Book book;\n  const BookSetDefault(this.book);\n  @override\n  List<Object> get props => [book];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/bloc/books/books_state.dart",
    "content": "part of 'books_bloc.dart';\n\n@immutable\nclass BooksState extends Equatable {\n\n  final LoadDataStatus status;\n  final List<Book> books;\n  final BookQueryRequest request;\n  final LoadDataStatus loadMoreStatus;\n  final LoadDataStatus deleteStatus;\n  final LoadDataStatus defaultStatus;\n\n  const BooksState({\n    this.status = LoadDataStatus.initial,\n    this.books = const <Book>[],\n    this.request = const BookQueryRequest(),\n    this.loadMoreStatus = LoadDataStatus.initial,\n    this.deleteStatus = LoadDataStatus.initial,\n    this.defaultStatus = LoadDataStatus.initial,\n  });\n\n  BooksState copyWith({\n    LoadDataStatus? status,\n    List<Book>? books,\n    BookQueryRequest? request,\n    LoadDataStatus? loadMoreStatus,\n    LoadDataStatus? deleteStatus,\n    LoadDataStatus? defaultStatus,\n  }) {\n    return BooksState(\n      status: status ?? this.status,\n      books: books ?? this.books,\n      request: request ?? this.request,\n      loadMoreStatus: loadMoreStatus ?? this.loadMoreStatus,\n      deleteStatus: deleteStatus ?? this.deleteStatus,\n      defaultStatus: defaultStatus ?? this.defaultStatus\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, books, request, loadMoreStatus, deleteStatus, defaultStatus];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/books.dart",
    "content": "export 'bloc/books/books_bloc.dart';\nexport 'bloc/book_fetch/book_fetch_bloc.dart';\nexport 'data/models/book.dart';\nexport 'data/models/book_query_request.dart';\nexport 'data/book_repository.dart';\nexport 'ui/books_page.dart';\nexport 'ui/book_detail_page.dart';\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/data/book_repository.dart",
    "content": "import 'dart:convert';\nimport '/commons/commons.dart';\nimport '/books/books.dart';\n\nclass BookRepository {\n\n  List<Book> _responseToList(String response) {\n    return (json.decode(response)['data']['content']).map<Book>((i) => Book.fromJson(i)).toList();\n  }\n\n  Book _responseBook(String response) {\n    return Book.fromJson(json.decode(response)['data']);\n  }\n\n  Future<List<Book>> query(BookQueryRequest request) async {\n    return _responseToList(await HttpClient().get('books', params: request.toJson()));\n  }\n\n  Future<Book> get(int id) async {\n    return _responseBook(await HttpClient().get('accounts/$id'));\n  }\n\n  Future<bool> setDefault(String id) async {\n    String response = await HttpClient().put('setDefaultBook/$id');\n    return parseResponse(response);\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/data/models/book.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\nimport '/commons/commons.dart';\n\npart 'book.g.dart';\n\n@JsonSerializable()\nclass Book extends Equatable {\n\n  final int id;\n  final String name;\n  final IdNameModel group;\n  final String? notes;\n  final bool descriptionEnable;\n  final bool timeEnable;\n  final bool imageEnable;\n  final IdNameModel? defaultExpenseAccount;\n  final IdNameModel? defaultIncomeAccount;\n  final IdNameModel? defaultTransferFromAccount;\n  final IdNameModel? defaultTransferToAccount;\n  final IdNameModel? defaultExpenseCategory;\n  final IdNameModel? defaultIncomeCategory;\n\n  const Book({\n    required this.id,\n    required this.name,\n    required this.group,\n    this.notes,\n    required this.descriptionEnable,\n    required this.timeEnable,\n    required this.imageEnable,\n    this.defaultExpenseAccount,\n    this.defaultIncomeAccount,\n    this.defaultTransferFromAccount,\n    this.defaultTransferToAccount,\n    this.defaultExpenseCategory,\n    this.defaultIncomeCategory,\n  });\n\n  factory Book.fromJson(Map<String, dynamic> json) => _$BookFromJson(json);\n\n  Map<String, dynamic> toJson() => _$BookToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/data/models/book.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'book.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nBook _$BookFromJson(Map<String, dynamic> json) => Book(\n      id: json['id'] as int,\n      name: json['name'] as String,\n      group: IdNameModel.fromJson(json['group'] as Map<String, dynamic>),\n      notes: json['notes'] as String?,\n      descriptionEnable: json['descriptionEnable'] as bool,\n      timeEnable: json['timeEnable'] as bool,\n      imageEnable: json['imageEnable'] as bool,\n      defaultExpenseAccount: json['defaultExpenseAccount'] == null\n          ? null\n          : IdNameModel.fromJson(\n              json['defaultExpenseAccount'] as Map<String, dynamic>),\n      defaultIncomeAccount: json['defaultIncomeAccount'] == null\n          ? null\n          : IdNameModel.fromJson(\n              json['defaultIncomeAccount'] as Map<String, dynamic>),\n      defaultTransferFromAccount: json['defaultTransferFromAccount'] == null\n          ? null\n          : IdNameModel.fromJson(\n              json['defaultTransferFromAccount'] as Map<String, dynamic>),\n      defaultTransferToAccount: json['defaultTransferToAccount'] == null\n          ? null\n          : IdNameModel.fromJson(\n              json['defaultTransferToAccount'] as Map<String, dynamic>),\n      defaultExpenseCategory: json['defaultExpenseCategory'] == null\n          ? null\n          : IdNameModel.fromJson(\n              json['defaultExpenseCategory'] as Map<String, dynamic>),\n      defaultIncomeCategory: json['defaultIncomeCategory'] == null\n          ? null\n          : IdNameModel.fromJson(\n              json['defaultIncomeCategory'] as Map<String, dynamic>),\n    );\n\nMap<String, dynamic> _$BookToJson(Book instance) => <String, dynamic>{\n      'id': instance.id,\n      'name': instance.name,\n      'group': instance.group,\n      'notes': instance.notes,\n      'descriptionEnable': instance.descriptionEnable,\n      'timeEnable': instance.timeEnable,\n      'imageEnable': instance.imageEnable,\n      'defaultExpenseAccount': instance.defaultExpenseAccount,\n      'defaultIncomeAccount': instance.defaultIncomeAccount,\n      'defaultTransferFromAccount': instance.defaultTransferFromAccount,\n      'defaultTransferToAccount': instance.defaultTransferToAccount,\n      'defaultExpenseCategory': instance.defaultExpenseCategory,\n      'defaultIncomeCategory': instance.defaultIncomeCategory,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/data/models/book_query_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'book_query_request.g.dart';\n\n@JsonSerializable()\nclass BookQueryRequest extends Equatable {\n\n  final int page;\n  final int size;\n\n  const BookQueryRequest({\n    this.page = 1,\n    this.size = 10,\n  });\n\n  BookQueryRequest copyWith({\n    int? page,\n    int? size,\n  }) {\n    return BookQueryRequest(\n      page: page ?? this.page,\n      size: size ?? this.size,\n    );\n  }\n\n  factory BookQueryRequest.fromJson(Map<String, dynamic> json) => _$BookQueryRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$BookQueryRequestToJson(this);\n\n  @override\n  List<Object?> get props => [page];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/data/models/book_query_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'book_query_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nBookQueryRequest _$BookQueryRequestFromJson(Map<String, dynamic> json) =>\n    BookQueryRequest(\n      page: json['page'] as int? ?? 1,\n      size: json['size'] as int? ?? 10,\n    );\n\nMap<String, dynamic> _$BookQueryRequestToJson(BookQueryRequest instance) =>\n    <String, dynamic>{\n      'page': instance.page,\n      'size': instance.size,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/ui/book_detail_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/login/bloc/auth/auth_bloc.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/books/books.dart';\n\nclass BookDetailPage extends StatefulWidget {\n\n  final Book book;\n  BookDetailPage({\n    required this.book\n  });\n\n  @override\n  State<BookDetailPage> createState() => _BookDetailPageState();\n}\n\nclass _BookDetailPageState extends State<BookDetailPage> {\n\n  @override\n  void initState() {\n    BlocProvider.of<BookFetchBloc>(context).add(BookLoadDefault(book: widget.book));\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<BooksBloc, BooksState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              // Navigator.pop(context);\n              if (Navigator.canPop(context)) {\n                Navigator.of(context).pop();\n              } else {\n                SystemNavigator.pop();\n              }\n            }\n          }\n        ),\n        BlocListener<BooksBloc, BooksState>(\n          listenWhen: (previous, current) => previous.defaultStatus != current.defaultStatus,\n          listener: (context, state) {\n            if (state.defaultStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n            }\n          },\n        )\n      ],\n      child: BlocBuilder<BookFetchBloc, BookFetchState>(\n        builder: (context, state) {\n          return Scaffold(\n            appBar: AppBar(\n              centerTitle: true,\n              title: const Text('账本详情'),\n              actions: _buildActions(context, state.book ?? widget.book)\n            ),\n            body: Builder(\n              builder: (context) {\n                switch (state.status) {\n                  case LoadDataStatus.progress:\n                  case LoadDataStatus.initial:\n                    return const PageLoading();\n                  case LoadDataStatus.success:\n                    return _buildBody(context, state.book ?? widget.book);\n                  default:\n                    return PageError(onTap: () { BlocProvider.of<AccountFetchBloc>(context).add(AccountFetched()); });\n                }\n              },\n            )\n          );\n        }\n      )\n    );\n  }\n\n  List<Widget> _buildActions(BuildContext context, Book book) {\n    return [\n      IconButton(\n        icon: Icon(Icons.edit),\n        onPressed: () {\n          //fullDialog(context, AccountFormPage(type: 2, accountType: account.type, account: account));\n        }\n      ),\n      IconButton(\n        icon: Icon(Icons.delete),\n        onPressed: () async {\n          if (await confirm(\n            context,\n            content: Text(\"确定删除${book.name}吗？\"),\n            textOK: Text(\"确定\"),\n            textCancel: Text(\"取消\"),\n          )) {\n            //BlocProvider.of<BooksBloc>(context).add(BookDeleted(book.id.toString()));\n          }\n        }\n      )\n    ];\n  }\n\n  Widget _buildBody(BuildContext context, Book book) {\n    final theme = Theme.of(context);\n    TextStyle? style1 = theme.textTheme.bodyText2;\n    TextStyle? style2 = theme.textTheme.bodyText1;\n    final state = context.watch<AuthBloc>().state;\n    return SingleChildScrollView(\n      child: Padding(\n        padding: EdgeInsets.symmetric(horizontal: 15),\n        child: Column(\n          children: [\n            OverflowBar(\n              overflowAlignment: OverflowBarAlignment.center,\n              spacing: 20,\n              children: [\n                ElevatedButton(\n                    child: const Text('设置'),\n                    onPressed: () {\n\n                    }\n                ),\n                ElevatedButton(\n                  child: const Text('设为默认'),\n                  onPressed: state.session?.defaultBook.id != book.id ? () {\n                    BlocProvider.of<BooksBloc>(context).add(BookSetDefault(book));\n                  } : null\n                )\n              ]\n            ),\n            SizedBox(height: 15),\n            Row(children: [Text(\"账本名称：\", style: style1), Text(book.name, style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"所属组：\", style: style1), Text(book.group.name, style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否展示描述：\", style: style1), Text(boolToString(book.descriptionEnable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否展示时间：\", style: style1), Text(boolToString(book.timeEnable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否支出图片：\", style: style1), Text(boolToString(book.imageEnable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"默认支出账户：\", style: style1), Text(book.defaultExpenseAccount?.name ?? '', style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"默认收入账户：\", style: style1), Text(book.defaultIncomeAccount?.name ?? '', style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"默认支出类别：\", style: style1), Text(book.defaultExpenseCategory?.name ?? '', style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"默认收入类别：\", style: style1), Text(book.defaultIncomeCategory?.name ?? '', style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"默认转出账户：\", style: style1), Text(book.defaultTransferFromAccount?.name ?? '', style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"默认转入账户：\", style: style1), Text(book.defaultTransferToAccount?.name ?? '', style: style2)]),\n          ],\n        )\n      ),\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/books/ui/books_page.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:pull_to_refresh/pull_to_refresh.dart';\nimport '/routes.dart';\nimport '/commons/commons.dart';\nimport '/books/books.dart';\nimport '/components/components.dart';\n\nclass BooksPage extends StatefulWidget {\n  @override\n  State<BooksPage> createState() => _BooksPageState();\n}\n\nclass _BooksPageState extends State<BooksPage> {\n\n  RefreshController _refreshController = RefreshController(initialRefresh: false);\n\n  @override\n  void initState() {\n    BlocProvider.of<BooksBloc>(context).add(BooksRefreshed());\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<BooksBloc, BooksState>(\n          listenWhen: (previous, current) =>\n          previous.loadMoreStatus != current.loadMoreStatus,\n          listener: (context, state) {\n            if (state.loadMoreStatus == LoadDataStatus.success) {\n              _refreshController.loadComplete();\n            } else if (state.loadMoreStatus == LoadDataStatus.failure) {\n              _refreshController.loadFailed();\n            } else if (state.loadMoreStatus == LoadDataStatus.empty) {\n              _refreshController.loadNoData();\n            }\n          },\n        )\n      ],\n      child: Scaffold(\n        appBar: AppBar(\n          centerTitle: true,\n          title: const Text('账本'),\n          actions: [\n            IconButton(\n              icon: const Icon(Icons.add),\n              onPressed: () {\n              }\n            )\n          ]\n        ),\n        body: BlocBuilder<BooksBloc, BooksState>(\n          buildWhen: (previous, current) => previous.status != current.status || previous.books != current.books,\n          builder: (context, state) {\n            switch (state.status) {\n              case LoadDataStatus.progress:\n              case LoadDataStatus.initial:\n                return const PageLoading();\n              case LoadDataStatus.success:\n                if (state.books.isEmpty) return Empty();\n                return SmartRefresher(\n                  enablePullDown: true,\n                  enablePullUp: true,\n                  controller: _refreshController,\n                  child: _buildList(context, state.books),\n                  onRefresh: () async {\n                    BlocProvider.of<BooksBloc>(context).add(BooksRefreshed());\n                    _refreshController.refreshCompleted();\n                  },\n                  onLoading: () async {\n                    BlocProvider.of<BooksBloc>(context).add(BooksLoadMore());\n                  },\n                );\n              default:\n                return PageError(onTap: () {\n                  BlocProvider.of<BooksBloc>(context).add(BooksRefreshed());\n                });\n            }\n          }\n        ),\n      )\n    );\n  }\n\n  Widget _buildList(BuildContext context, List<Book> books) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      itemCount: books.length,\n      itemBuilder: (context, index) {\n        Book book = books[index];\n        return ListTile(\n          dense: false,\n          title: Text(book.name, style: theme.textTheme.bodyText1),\n          subtitle: book.notes != null && book.notes!.isNotEmpty ? Text(book.notes!, style: theme.textTheme.caption) : null,\n          trailing: Icon(Icons.keyboard_arrow_right),\n          onTap: () {\n            Navigator.pushNamed(context, '/book-detail', arguments: BookDetailArguments(book: book));\n          },\n        );\n      },\n      separatorBuilder: (context, index) {\n        return Divider();\n      },\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_fetch/category_fetch_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\nimport '/categories/categories.dart';\nimport '/commons/commons.dart';\n\npart 'category_fetch_event.dart';\n\npart 'category_fetch_state.dart';\n\nclass CategoryFetchBloc extends Bloc<CategoryFetchEvent, CategoryFetchState> {\n\n  final CategoryRepository categoryRepository;\n\n  CategoryFetchBloc({\n    required this.categoryRepository,\n  }) : super(CategoryFetchState()) {\n    on<CategoryFetched>(_onFetched);\n    on<CategoryLoadDefault>(_onDefault);\n  }\n\n  void _onDefault(CategoryLoadDefault event, Emitter<CategoryFetchState> emit) {\n    emit(state.copyWith(\n      status: LoadDataStatus.success,\n      category: event.category\n    ));\n  }\n\n  void _onFetched(_, Emitter<CategoryFetchState> emit) async {\n    try {\n      emit(state.copyWith(status: LoadDataStatus.progress));\n      final category = await categoryRepository.get(state.category!.id);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        category: category,\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_fetch/category_fetch_event.dart",
    "content": "part of 'category_fetch_bloc.dart';\n\n@immutable\nclass CategoryFetchEvent extends Equatable {\n  const CategoryFetchEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass CategoryFetched extends CategoryFetchEvent {}\n\nclass CategoryLoadDefault extends CategoryFetchEvent {\n  final Category category;\n  const CategoryLoadDefault({\n    required this.category,\n  });\n  List<Object> get props => [category];\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_fetch/category_fetch_state.dart",
    "content": "part of 'category_fetch_bloc.dart';\n\n@immutable\nclass CategoryFetchState extends Equatable {\n\n  final LoadDataStatus status;\n  final Category? category;\n\n  const CategoryFetchState({\n    this.status = LoadDataStatus.initial,\n    this.category,\n  });\n\n  CategoryFetchState copyWith({\n    LoadDataStatus? status,\n    Category? category,\n  }) {\n    return CategoryFetchState(\n      status: status ?? this.status,\n      category: category ?? this.category,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, category];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_form/category_form_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport 'package:meta/meta.dart';\nimport '/categories/categories.dart';\n\npart 'category_form_event.dart';\npart 'category_form_state.dart';\n\nclass CategoryFormBloc extends Bloc<CategoryFormEvent, CategoryFormState> {\n\n  final CategoryRepository categoryRepository;\n  final ExpenseCategorySelectBloc expenseCategorySelectBloc;\n  final IncomeCategorySelectBloc incomeCategorySelectBloc;\n\n  CategoryFormBloc({\n    required this.categoryRepository,\n    required this.expenseCategorySelectBloc,\n    required this.incomeCategorySelectBloc\n  }) : super(const CategoryFormState()) {\n    on<CategoryFormNameChanged>(_onNameChanged);\n    on<CategoryFormNotesChanged>(_onNotesChanged);\n    on<CategoryFormParentChanged>(_onParentChanged);\n    on<CategoryFormDefaultLoaded>(_onDefaultLoaded);\n    on<CategoryFormSubmitted>(_onSubmitted);\n  }\n\n  void _onNameChanged(CategoryFormNameChanged event, Emitter<CategoryFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(name: event.name),\n    ));\n  }\n\n  void _onNotesChanged(CategoryFormNotesChanged event, Emitter<CategoryFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(notes: event.notes),\n    ));\n  }\n\n  void _onParentChanged(CategoryFormParentChanged event, Emitter<CategoryFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(parentId: event.parentId),\n    ));\n  }\n\n  void _onDefaultLoaded(CategoryFormDefaultLoaded event, Emitter<CategoryFormState> emit) {\n    emit(state.copyWith(\n      status: FormzStatus.pure,\n      request: state.request.copyWith(\n        name: event.type == 2 ? event.category?.name ?? '' : '',\n        notes: event.type == 2 ? event.category?.notes ?? '' : '',\n        parentId: event.type == 2 ? event.category?.parentId.toString() : event.category?.id.toString()\n      )\n    ));\n  }\n\n  void _onSubmitted(CategoryFormSubmitted event, Emitter<CategoryFormState> emit) async {\n    try {\n      bool result = false;\n      switch (event.type) {\n        case 1:\n          result = await categoryRepository.add(event.categoryType, state.request);\n          break;\n        case 2:\n          result = await categoryRepository.update(event.categoryType, event.category!.id, state.request);\n          break;\n        default:\n          break;\n      }\n      if (result) {\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n        if (event.categoryType == 1) {\n          expenseCategorySelectBloc.add(ExpenseCategorySelectLoaded());\n        } else if (event.categoryType == 2) {\n          incomeCategorySelectBloc.add(IncomeCategorySelectLoaded());\n        }\n      } else {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: FormzStatus.submissionFailure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_form/category_form_event.dart",
    "content": "part of 'category_form_bloc.dart';\n\n@immutable\nabstract class CategoryFormEvent extends Equatable {\n  const CategoryFormEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass CategoryFormNameChanged extends CategoryFormEvent {\n  const CategoryFormNameChanged(this.name);\n  final String name;\n  @override\n  List<Object> get props => [name];\n}\n\nclass CategoryFormNotesChanged extends CategoryFormEvent {\n  const CategoryFormNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass CategoryFormParentChanged extends CategoryFormEvent {\n  const CategoryFormParentChanged(this.parentId);\n  final String parentId;\n  @override\n  List<Object> get props => [parentId];\n}\n\nclass CategoryFormDefaultLoaded extends CategoryFormEvent {\n  final int type;\n  final int categoryType;\n  final Category? category;\n  const CategoryFormDefaultLoaded(this.type, this.categoryType, this.category);\n  @override\n  List<Object?> get props => [type, categoryType, category];\n}\n\nclass CategoryFormSubmitted extends CategoryFormEvent {\n  final int type;\n  final int categoryType;\n  final Category? category;\n  const CategoryFormSubmitted(this.type, this.categoryType, this.category);\n  @override\n  List<Object?> get props => [type, categoryType, category];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_form/category_form_state.dart",
    "content": "part of 'category_form_bloc.dart';\n\n@immutable\nclass CategoryFormState extends Equatable {\n\n  final FormzStatus status;\n  final CategoryFormRequest request;\n\n  const CategoryFormState({\n    this.status = FormzStatus.pure,\n    this.request = const CategoryFormRequest(),\n  });\n\n  CategoryFormState copyWith({\n    FormzStatus? status,\n    CategoryFormRequest? request,\n  }) {\n    return CategoryFormState(\n      status: status ?? this.status,\n      request: request ?? this.request,\n    );\n  }\n\n  @override\n  List<Object> get props => [status, request];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_tree/category_tree_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\nimport '/categories/categories.dart';\nimport '/commons/commons.dart';\n\npart 'category_tree_event.dart';\npart 'category_tree_state.dart';\n\nclass CategoryTreeBloc extends Bloc<CategoryTreeEvent, CategoryTreeState> {\n\n  final CategoryRepository categoryRepository;\n  final ExpenseCategorySelectBloc expenseCategorySelectBloc;\n  final IncomeCategorySelectBloc incomeCategorySelectBloc;\n\n  CategoryTreeBloc({\n    required this.categoryRepository,\n    required this.expenseCategorySelectBloc,\n    required this.incomeCategorySelectBloc\n  }) : super(CategoryTreeState()) {\n    on<ExpenseCategoryTreeRefreshed>(_onExpenseCategoryRefreshed);\n    on<IncomeCategoryTreeRefreshed>(_onIncomeCategoryRefreshed);\n    on<CategoryItemClicked>(_onItemClicked);\n    on<CategoryBackClicked>(_onBackClicked);\n    on<CategoryToggled>(_onToggled);\n    on<CategoryDeleted>(_onDeleted);\n  }\n\n  void _onExpenseCategoryRefreshed(_, Emitter<CategoryTreeState> emit) async {\n    try {\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n      ));\n      final categories = await categoryRepository.queryExpense();\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        categories: categories,\n        currentCategories: categories\n      ));\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onIncomeCategoryRefreshed(_, Emitter<CategoryTreeState> emit) async {\n    try {\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n      ));\n      final categories = await categoryRepository.queryIncome();\n      emit(state.copyWith(\n          status: LoadDataStatus.success,\n          categories: categories,\n          currentCategories: categories\n      ));\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onItemClicked(CategoryItemClicked event, Emitter<CategoryTreeState> emit) async {\n    List<List<CategoryTree>> newHistory = List.from(state.history);\n    newHistory.add(state.currentCategories);\n    emit(state.copyWith(\n      currentLevel: state.currentLevel + 1,\n      currentCategories: event.categoryTree.children,\n      history: newHistory,\n    ));\n  }\n\n  void _onBackClicked(CategoryBackClicked event, Emitter<CategoryTreeState> emit) async {\n    emit(state.copyWith(\n      currentLevel: state.currentLevel - 1,\n      currentCategories: state.history.last\n    ));\n  }\n\n  void _onToggled(CategoryToggled event, Emitter<CategoryTreeState> emit) async {\n    try {\n      emit(state.copyWith(toggleStatus: LoadDataStatus.progress));\n      final result = await categoryRepository.toggle(event.id);\n      if (result) {\n        emit(state.copyWith(toggleStatus: LoadDataStatus.success));\n        expenseCategorySelectBloc.add(ExpenseCategorySelectLoaded());\n        incomeCategorySelectBloc.add(IncomeCategorySelectLoaded());\n      } else {\n        emit(state.copyWith(toggleStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(toggleStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onDeleted(CategoryDeleted event, Emitter<CategoryTreeState> emit) async {\n    try {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.progress));\n      final result = await categoryRepository.delete(event.id);\n      if (result) {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.success));\n        expenseCategorySelectBloc.add(ExpenseCategorySelectLoaded());\n        incomeCategorySelectBloc.add(IncomeCategorySelectLoaded());\n      } else {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_tree/category_tree_event.dart",
    "content": "part of 'category_tree_bloc.dart';\n\n@immutable\nabstract class CategoryTreeEvent extends Equatable {\n  const CategoryTreeEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass ExpenseCategoryTreeRefreshed extends CategoryTreeEvent { }\n\nclass IncomeCategoryTreeRefreshed extends CategoryTreeEvent { }\n\nclass CategoryItemClicked extends CategoryTreeEvent {\n  final CategoryTree categoryTree;\n  const CategoryItemClicked({\n    required this.categoryTree,\n  });\n}\n\nclass CategoryBackClicked extends CategoryTreeEvent { }\n\nclass CategoryDeleted extends CategoryTreeEvent {\n  final String id;\n  const CategoryDeleted(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass CategoryToggled extends CategoryTreeEvent {\n  final String id;\n  const CategoryToggled(this.id);\n  @override\n  List<Object> get props => [id];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/category_tree/category_tree_state.dart",
    "content": "part of 'category_tree_bloc.dart';\n\n@immutable\nclass CategoryTreeState extends Equatable {\n\n  final LoadDataStatus status;\n  final List<CategoryTree> categories;\n  final LoadDataStatus deleteStatus;\n  final LoadDataStatus toggleStatus;\n  final List<CategoryTree> currentCategories;\n  final int currentLevel;\n  final List<List<CategoryTree>> history;\n\n  @override\n  List<Object?> get props => [status, categories, deleteStatus, toggleStatus, currentCategories, currentLevel, history];\n\n  const CategoryTreeState({\n    this.status = LoadDataStatus.initial,\n    this.categories = const <CategoryTree>[],\n    this.deleteStatus = LoadDataStatus.initial,\n    this.toggleStatus = LoadDataStatus.initial,\n    this.currentCategories = const <CategoryTree>[],\n    this.currentLevel = 1,\n    this.history = const <List<CategoryTree>>[],\n  });\n\n  CategoryTreeState copyWith({\n    LoadDataStatus? status,\n    List<CategoryTree>? categories,\n    LoadDataStatus? deleteStatus,\n    LoadDataStatus? toggleStatus,\n    List<CategoryTree>? currentCategories,\n    int? currentLevel,\n    List<List<CategoryTree>>? history\n  }) {\n    return CategoryTreeState(\n      status: status ?? this.status,\n      categories: categories ?? this.categories,\n      deleteStatus: deleteStatus ?? this.deleteStatus,\n      toggleStatus: toggleStatus ?? this.toggleStatus,\n      currentCategories: currentCategories ?? this.currentCategories,\n      currentLevel: currentLevel ?? this.currentLevel,\n      history: history ?? this.history\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/expense_category_select/expense_category_select_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/categories/categories.dart';\n\npart 'expense_category_select_event.dart';\npart 'expense_category_select_state.dart';\n\nclass ExpenseCategorySelectBloc extends Bloc<ExpenseCategorySelectEvent, ExpenseCategorySelectState> {\n\n  final CategoryRepository categoryRepository;\n\n  ExpenseCategorySelectBloc({\n    required this.categoryRepository\n  }) : super(ExpenseCategorySelectStateLoadInProgress()) {\n    on<ExpenseCategorySelectLoaded>(_onExpenseCategorySelectLoaded);\n  }\n\n  void _onExpenseCategorySelectLoaded(_, Emitter<ExpenseCategorySelectState> emit) async {\n    // 只加载一次数据\n    // if (state is ExpenseCategorySelectStateLoadSuccess) return;\n    try {\n      emit(ExpenseCategorySelectStateLoadInProgress());\n      final categories = await categoryRepository.getExpenseable();\n      emit(ExpenseCategorySelectStateLoadSuccess(categories));\n    } catch (_) {\n      emit(ExpenseCategorySelectStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/expense_category_select/expense_category_select_event.dart",
    "content": "part of 'expense_category_select_bloc.dart';\n\nabstract class ExpenseCategorySelectEvent extends Equatable {\n\n  const ExpenseCategorySelectEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass ExpenseCategorySelectLoaded extends ExpenseCategorySelectEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/expense_category_select/expense_category_select_state.dart",
    "content": "part of 'expense_category_select_bloc.dart';\n\n\nabstract class ExpenseCategorySelectState extends Equatable {\n\n  const ExpenseCategorySelectState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass ExpenseCategorySelectStateLoadInProgress extends ExpenseCategorySelectState { }\n\nclass ExpenseCategorySelectStateLoadSuccess extends ExpenseCategorySelectState {\n\n  final List<Category> categories;\n\n  const ExpenseCategorySelectStateLoadSuccess(this.categories);\n\n  @override\n  List<Object> get props => [categories];\n\n}\n\nclass ExpenseCategorySelectStateLoadFailure extends ExpenseCategorySelectState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/income_category_select/income_category_select_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/categories/categories.dart';\n\npart 'income_category_select_event.dart';\npart 'income_category_select_state.dart';\n\nclass IncomeCategorySelectBloc extends Bloc<IncomeCategorySelectEvent, IncomeCategorySelectState> {\n\n  final CategoryRepository categoryRepository;\n\n  IncomeCategorySelectBloc({\n    required this.categoryRepository\n  }) : super(IncomeCategorySelectStateLoadInProgress()) {\n    on<IncomeCategorySelectLoaded>(_onIncomeCategorySelectLoaded);\n  }\n\n  void _onIncomeCategorySelectLoaded(_, Emitter<IncomeCategorySelectState> emit) async {\n    // 只加载一次数据\n    // if (state is IncomeCategorySelectStateLoadSuccess) return;\n    try {\n      emit(IncomeCategorySelectStateLoadInProgress());\n      final categories = await categoryRepository.getIncomeable();\n      emit(IncomeCategorySelectStateLoadSuccess(categories));\n    } catch (_) {\n      emit(IncomeCategorySelectStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/income_category_select/income_category_select_event.dart",
    "content": "part of 'income_category_select_bloc.dart';\n\nabstract class IncomeCategorySelectEvent extends Equatable {\n\n  const IncomeCategorySelectEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass IncomeCategorySelectLoaded extends IncomeCategorySelectEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/bloc/income_category_select/income_category_select_state.dart",
    "content": "part of 'income_category_select_bloc.dart';\n\n\nabstract class IncomeCategorySelectState extends Equatable {\n\n  const IncomeCategorySelectState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass IncomeCategorySelectStateLoadInProgress extends IncomeCategorySelectState { }\n\nclass IncomeCategorySelectStateLoadSuccess extends IncomeCategorySelectState {\n\n  final List<Category> categories;\n\n  const IncomeCategorySelectStateLoadSuccess(this.categories);\n\n  @override\n  List<Object> get props => [categories];\n\n}\n\nclass IncomeCategorySelectStateLoadFailure extends IncomeCategorySelectState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/categories.dart",
    "content": "export 'bloc/expense_category_select/expense_category_select_bloc.dart';\nexport 'bloc/income_category_select/income_category_select_bloc.dart';\nexport 'bloc/category_tree/category_tree_bloc.dart';\nexport 'bloc/category_fetch/category_fetch_bloc.dart';\nexport 'bloc/category_form/category_form_bloc.dart';\nexport 'data/category_repository.dart';\nexport 'data/models/category.dart';\nexport 'data/models/category_tree.dart';\nexport 'data/models/category_form_request.dart';\nexport 'ui/expense_categories_page.dart';\nexport 'ui/income_categories_page.dart';\nexport 'ui/category_detail_page.dart';\nexport 'ui/category_form_page.dart';\nexport 'ui/expense_category_form_page.dart';\nexport 'ui/income_category_form_page.dart';\nexport 'ui/widgets/category_form/name_input.dart';\nexport 'ui/widgets/category_form/notes_input.dart';\nexport 'ui/widgets/category_form/expense_category_input.dart';\nexport 'ui/widgets/category_form/income_category_input.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/data/category_repository.dart",
    "content": "import 'dart:convert';\n\nimport 'models/category.dart';\nimport '/commons/commons.dart';\nimport '/categories/categories.dart';\n\nclass CategoryRepository {\n\n  List<Category> _responseToList(String response) {\n    return (json.decode(response)['data']).map<Category>((i) => Category.fromJson(i)).toList();\n  }\n\n  Future<List<Category>> getExpenseable() async {\n    String response = await HttpClient().get('expense-categories/enable');\n    return _responseToList(response);\n  }\n\n  Future<List<Category>> getIncomeable() async {\n    String response = await HttpClient().get('income-categories/enable');\n    return _responseToList(response);\n  }\n\n  Future<List<CategoryTree>> queryExpense() async {\n    String response = await HttpClient().get('expense-categories');\n    return (json.decode(response)['data']).map<CategoryTree>((i) => CategoryTree.fromJson(i)).toList();\n  }\n\n  Future<List<CategoryTree>> queryIncome() async {\n    String response = await HttpClient().get('income-categories');\n    return (json.decode(response)['data']).map<CategoryTree>((i) => CategoryTree.fromJson(i)).toList();\n  }\n\n  Future<Category> get(int id) async {\n    String response = await HttpClient().get('categories/$id');\n    return Category.fromJson(json.decode(response)['data']);\n  }\n\n  Future<bool> toggle(String id) async {\n    String response = await HttpClient().put('categories/$id/toggle');\n    return parseResponse(response);\n  }\n\n  Future<bool> delete(String id) async {\n    String response = await HttpClient().delete('categories/$id');\n    return parseResponse(response);\n  }\n\n  Future<bool> add(int type, CategoryFormRequest request) async {\n    String response = '';\n    switch(type) {\n      case 1:\n        response = await HttpClient().post('expense-categories', data: request.toJson());\n        return parseResponse(response);\n      case 2:\n        response = await HttpClient().post('income-categories', data: request.toJson());\n        return parseResponse(response);\n      default:\n        throw('type error');\n    }\n  }\n\n  Future<bool> update(int type, int id, CategoryFormRequest request) async {\n    String response = '';\n    switch(type) {\n      case 1:\n        response = await HttpClient().put('expense-categories/$id', data: request.toJson());\n        return parseResponse(response);\n      case 2:\n        response = await HttpClient().put('income-categories/$id', data: request.toJson());\n        return parseResponse(response);\n      default:\n        throw('type error');\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/data/models/category.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\nimport '/categories/categories.dart';\n\npart 'category.g.dart';\n\n@JsonSerializable()\nclass Category extends Equatable {\n\n  final int id;\n  final String name;\n  final String? notes;\n  final bool enable;\n  final int? parentId;\n  final String? parentName;\n\n  Category({\n    required this.id,\n    required this.name,\n    this.notes,\n    required this.enable,\n    this.parentId,\n    this.parentName\n  });\n\n  factory Category.fromJson(Map<String, dynamic> json) => _$CategoryFromJson(json);\n\n  Map<String, dynamic> toJson() => _$CategoryToJson(this);\n\n  factory Category.fromTree(CategoryTree categoryTree) => Category(\n      id: categoryTree.id,\n      name: categoryTree.name,\n      notes: categoryTree.notes,\n      enable: categoryTree.enable,\n      parentId: categoryTree.parentId,\n      parentName: categoryTree.parentName\n  );\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/data/models/category.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'category.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nCategory _$CategoryFromJson(Map<String, dynamic> json) => Category(\n      id: json['id'] as int,\n      name: json['name'] as String,\n      notes: json['notes'] as String?,\n      enable: json['enable'] as bool,\n      parentId: json['parentId'] as int?,\n      parentName: json['parentName'] as String?,\n    );\n\nMap<String, dynamic> _$CategoryToJson(Category instance) => <String, dynamic>{\n      'id': instance.id,\n      'name': instance.name,\n      'notes': instance.notes,\n      'enable': instance.enable,\n      'parentId': instance.parentId,\n      'parentName': instance.parentName,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/data/models/category_form_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'category_form_request.g.dart';\n\n@JsonSerializable()\nclass CategoryFormRequest extends Equatable {\n\n  final String? name;\n  final String? notes;\n  final String? parentId;\n\n  const CategoryFormRequest({\n    this.name,\n    this.notes,\n    this.parentId,\n  });\n\n  Map<String, dynamic> toJson() => _$CategoryFormRequestToJson(this);\n\n  CategoryFormRequest copyWith({\n    String? name,\n    String? notes,\n    String? parentId\n  }) {\n    return CategoryFormRequest(\n      name: name ?? this.name,\n      notes: notes ?? this.notes,\n      parentId: parentId ?? this.parentId\n    );\n  }\n\n  @override\n  List<Object?> get props => [name, notes, parentId];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/data/models/category_form_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'category_form_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nCategoryFormRequest _$CategoryFormRequestFromJson(Map<String, dynamic> json) =>\n    CategoryFormRequest(\n      name: json['name'] as String?,\n      notes: json['notes'] as String?,\n      parentId: json['parentId'] as String?,\n    );\n\nMap<String, dynamic> _$CategoryFormRequestToJson(\n        CategoryFormRequest instance) =>\n    <String, dynamic>{\n      'name': instance.name,\n      'notes': instance.notes,\n      'parentId': instance.parentId,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/data/models/category_tree.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'category_tree.g.dart';\n\n@JsonSerializable()\nclass CategoryTree extends Equatable {\n\n  final int id;\n  final String name;\n  final String? notes;\n  final bool enable;\n  final int? parentId;\n  final String? parentName;\n  final List<CategoryTree>? children;\n\n  CategoryTree({\n    required this.id,\n    required this.name,\n    this.notes,\n    required this.enable,\n    this.parentId,\n    this.parentName,\n    this.children\n  });\n\n  factory CategoryTree.fromJson(Map<String, dynamic> json) => _$CategoryTreeFromJson(json);\n\n  Map<String, dynamic> toJson() => _$CategoryTreeToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/data/models/category_tree.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'category_tree.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nCategoryTree _$CategoryTreeFromJson(Map<String, dynamic> json) => CategoryTree(\n      id: json['id'] as int,\n      name: json['name'] as String,\n      notes: json['notes'] as String?,\n      enable: json['enable'] as bool,\n      parentId: json['parentId'] as int?,\n      parentName: json['parentName'] as String?,\n      children: (json['children'] as List<dynamic>?)\n          ?.map((e) => CategoryTree.fromJson(e as Map<String, dynamic>))\n          .toList(),\n    );\n\nMap<String, dynamic> _$CategoryTreeToJson(CategoryTree instance) =>\n    <String, dynamic>{\n      'id': instance.id,\n      'name': instance.name,\n      'notes': instance.notes,\n      'enable': instance.enable,\n      'parentId': instance.parentId,\n      'parentName': instance.parentName,\n      'children': instance.children,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/category_detail_page.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/categories/categories.dart';\nimport '/commons/commons.dart';\nimport '/components/components.dart';\n\nclass CategoryDetailPage extends StatefulWidget {\n\n  final int categoryType;\n  final Category category;\n\n  CategoryDetailPage({\n    required this.category,\n    required this.categoryType\n  });\n\n  @override\n  State<CategoryDetailPage> createState() => _CategoryDetailPageState();\n\n}\n\nclass _CategoryDetailPageState extends State<CategoryDetailPage> {\n  @override\n  void initState() {\n    BlocProvider.of<CategoryFetchBloc>(context).add(CategoryLoadDefault(category: widget.category));\n    super.initState();\n  }\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<CategoryTreeBloc, CategoryTreeState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              if (Navigator.canPop(context)) {\n                Navigator.of(context).pop();\n              } else {\n                SystemNavigator.pop();\n              }\n            }\n          }\n        ),\n        BlocListener<CategoryTreeBloc, CategoryTreeState>(\n          listenWhen: (previous, current) => previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              BlocProvider.of<CategoryFetchBloc>(context).add(CategoryFetched());\n            }\n          },\n        )\n      ],\n      child: BlocBuilder<CategoryFetchBloc, CategoryFetchState>(\n        builder: (context, state) {\n          return Scaffold(\n            appBar: AppBar(\n              centerTitle: true,\n              title: const Text('类别详情'),\n              actions: _buildActions(context, state.category ?? widget.category)\n            ),\n              body: Builder(\n                builder: (context) {\n                  switch (state.status) {\n                    case LoadDataStatus.progress:\n                    case LoadDataStatus.initial:\n                      return const PageLoading();\n                    case LoadDataStatus.success:\n                      return _buildBody(context, state.category ?? widget.category);\n                    default:\n                      return PageError(onTap: () { BlocProvider.of<CategoryFetchBloc>(context).add(CategoryFetched()); });\n                  }\n                },\n              )\n          );\n        }\n      )\n    );\n  }\n\n  List<Widget> _buildActions(BuildContext context, Category category) {\n    return [\n      IconButton(\n        icon: const Icon(Icons.add),\n        onPressed: () {\n          fullDialog(context, CategoryFormPage(type: 1, categoryType: widget.categoryType, category: category));\n        }\n      ),\n      IconButton(\n        icon: Icon(Icons.edit),\n        onPressed: () {\n          fullDialog(context, CategoryFormPage(type: 2, categoryType: widget.categoryType, category: category));\n        }\n      ),\n      IconButton(\n          icon: Icon(Icons.delete),\n          onPressed: () async {\n            if (await confirm(\n              context,\n              content: Text(\"确定删除${category.name}吗？\"),\n              textOK: Text(\"确定\"),\n              textCancel: Text(\"取消\"),\n            )) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(CategoryDeleted(category.id.toString()));\n            }\n          }\n      )\n    ];\n  }\n\n  Widget _buildBody(BuildContext context, Category category) {\n    final theme = Theme.of(context);\n    TextStyle? style1 = theme.textTheme.bodyText2;\n    TextStyle? style2 = theme.textTheme.bodyText1;\n    return SingleChildScrollView(\n      child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              SizedBox(height: 20),\n              Row(children: [Text(\"名称：\", style: style1), Text(category.name, style: style2)]),\n              SizedBox(height: 15),\n              Row(children: [Text(\"父级名称：\", style: style1), Text(category.parentName ?? '', style: style2)]),\n              SizedBox(height: 15),\n              Row(children: [Text(\"是否可用：\", style: style1), Text(boolToString(category.enable), style: style2)]),\n              SizedBox(height: 15),\n              Row(children: [Text(\"备注：\", style: style1), Flexible(child: Text(category.notes ?? '', style: style2))]),\n              SizedBox(height: 15),\n              SizedBox(\n                width: double.infinity,\n                child: ElevatedButton(\n                  child: Text(category.enable ? '禁用' : '启用'),\n                  onPressed: () {\n                    BlocProvider.of<CategoryTreeBloc>(context).add(CategoryToggled(category.id.toString()));\n                  }\n                ),\n              )\n            ],\n          )\n      ),\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/category_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/categories/categories.dart';\n\nclass CategoryFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final int categoryType;\n  final Category? category;\n\n  const CategoryFormPage({\n    required this.type,\n    required this.categoryType,\n    this.category,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => CategoryFormBloc(\n        categoryRepository: RepositoryProvider.of<CategoryRepository>(context),\n        expenseCategorySelectBloc: BlocProvider.of<ExpenseCategorySelectBloc>(context),\n        incomeCategorySelectBloc: BlocProvider.of<IncomeCategorySelectBloc>(context)\n      )..add(CategoryFormDefaultLoaded(type, categoryType, category)),\n      child: BlocListener<CategoryFormBloc, CategoryFormState>(\n        listener: (context, state) {\n          if (state.status == FormzStatus.submissionSuccess) {\n            Message.success('操作成功');\n            Navigator.of(context).pop();\n            if (categoryType == 1) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(ExpenseCategoryTreeRefreshed());\n            } else if (categoryType == 2) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(IncomeCategoryTreeRefreshed());\n            }\n            if (type == 2) {\n              BlocProvider.of<CategoryFetchBloc>(context).add(CategoryFetched());\n            }\n          }\n        },\n        child: Builder(\n          builder: (context) {\n            switch (categoryType) {\n              case 1:\n                return ExpenseCategoryFormPage(type: type, category: category);\n              case 2:\n                return IncomeCategoryFormPage(type: type, category: category);\n              default:\n                return PageError(msg: '分类类型错误');\n            }\n          },\n        )\n      )\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/expense_categories_page.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/routes.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/categories/categories.dart';\n\nclass ExpenseCategoryPage extends StatefulWidget {\n  @override\n  State<ExpenseCategoryPage> createState() => _ExpenseCategoryPageState();\n}\n\nclass _ExpenseCategoryPageState extends State<ExpenseCategoryPage> {\n\n  @override\n  void initState() {\n    BlocProvider.of<CategoryTreeBloc>(context).add(ExpenseCategoryTreeRefreshed());\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final state = context.watch<CategoryTreeBloc>().state;\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<CategoryTreeBloc, CategoryTreeState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(ExpenseCategoryTreeRefreshed());\n            }\n          },\n        ),\n        BlocListener<CategoryTreeBloc, CategoryTreeState>(\n          listenWhen: (previous, current) => previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(ExpenseCategoryTreeRefreshed());\n            }\n          },\n        ),\n      ],\n      child: WillPopScope(\n        onWillPop: () => _onWillPop(state),\n        child: BlocBuilder<CategoryTreeBloc, CategoryTreeState>(\n          builder: (context, state) {\n            return Scaffold(\n              appBar: AppBar(\n                title: const Text('支出类别'),\n                centerTitle: true,\n                actions: [\n                  IconButton(\n                    icon: const Icon(Icons.add),\n                    onPressed: () {\n                      fullDialog(context, CategoryFormPage(type: 1, categoryType: 1));\n                    }\n                  )\n                ]\n              ),\n              body: Builder(\n                builder: (BuildContext context) {\n                  switch (state.status) {\n                    case LoadDataStatus.progress:\n                    case LoadDataStatus.initial:\n                      return const PageLoading();\n                    case LoadDataStatus.success:\n                      if (state.currentCategories.isEmpty) return Empty();\n                      return _buildList(context, state.currentCategories);\n                    default:\n                      return PageError(onTap: () { BlocProvider.of<CategoryTreeBloc>(context).add(ExpenseCategoryTreeRefreshed()); });\n                  }\n                },\n              ),\n            );\n          },\n        )\n      ),\n    );\n  }\n\n  Widget _buildList(BuildContext context, List<CategoryTree> categories) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      itemCount: categories.length,\n      itemBuilder: (context, index) {\n        CategoryTree categoryTree = categories[index];\n        return ListTile(\n          dense: true,\n          title: Text(categoryTree.name, style: theme.textTheme.bodyText1),\n          subtitle: categoryTree.notes != null && categoryTree.notes!.isNotEmpty ? Text(categoryTree.notes!, style: theme.textTheme.caption) : null,\n          trailing: categoryTree.children != null && categoryTree.children!.isNotEmpty ? Icon(Icons.keyboard_arrow_right) : null,\n          onTap: () {\n            if (categoryTree.children != null && categoryTree.children!.isNotEmpty) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(CategoryItemClicked(categoryTree: categoryTree));\n            } else {\n              Navigator.pushNamed(context, '/category-detail', arguments: CategoryDetailArguments(category: Category.fromTree(categoryTree), categoryType: 1));\n            }\n          },\n          onLongPress: () {\n            Navigator.pushNamed(context, '/category-detail', arguments: CategoryDetailArguments(category: Category.fromTree(categoryTree), categoryType: 1));\n          },\n        );\n      },\n      separatorBuilder: (context, index) {\n        return Divider();\n      },\n    );\n  }\n\n  Future<bool> _onWillPop(state) async {\n    if (state.currentLevel == 1) return true;\n    BlocProvider.of<CategoryTreeBloc>(context).add(CategoryBackClicked());\n    return false;\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/expense_category_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/categories/categories.dart';\n\nclass ExpenseCategoryFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final Category? category;\n\n  const ExpenseCategoryFormPage({\n    required this.type,\n    this.category\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        centerTitle: true,\n        title: Text(_buildTitle(type)),\n        actions: [\n          IconButton(\n            icon: Icon(Icons.done),\n            onPressed: () {\n              BlocProvider.of<CategoryFormBloc>(context).add(CategoryFormSubmitted(type, 1, category));\n            }\n          )\n        ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              ExpenseCategoryInput(),\n              NameInput(),\n              SizedBox(height: 10),\n              NotesInput(),\n              SizedBox(height: 10),\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增支出分类';\n      case 2:\n        return '修改支出分类';\n      default:\n        return '操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/income_categories_page.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/routes.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/categories/categories.dart';\n\nclass IncomeCategoryPage extends StatefulWidget {\n  @override\n  State<IncomeCategoryPage> createState() => _IncomeCategoryPageState();\n}\n\nclass _IncomeCategoryPageState extends State<IncomeCategoryPage> {\n\n  @override\n  void initState() {\n    BlocProvider.of<CategoryTreeBloc>(context).add(IncomeCategoryTreeRefreshed());\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final state = context.watch<CategoryTreeBloc>().state;\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<CategoryTreeBloc, CategoryTreeState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(IncomeCategoryTreeRefreshed());\n            }\n          },\n        ),\n        BlocListener<CategoryTreeBloc, CategoryTreeState>(\n          listenWhen: (previous, current) => previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(IncomeCategoryTreeRefreshed());\n            }\n          },\n        ),\n      ],\n      child: WillPopScope(\n        onWillPop: () => _onWillPop(state),\n        child: BlocBuilder<CategoryTreeBloc, CategoryTreeState>(\n          builder: (context, state) {\n            return Scaffold(\n              appBar: AppBar(\n                title: const Text('收入类别'),\n                centerTitle: true,\n                actions: [\n                  IconButton(\n                    icon: const Icon(Icons.add),\n                    onPressed: () {\n                      fullDialog(context, CategoryFormPage(type: 1, categoryType: 2));\n                    }\n                  )\n                ]\n              ),\n              body: Builder(\n                builder: (BuildContext context) {\n                  switch (state.status) {\n                    case LoadDataStatus.progress:\n                    case LoadDataStatus.initial:\n                      return const PageLoading();\n                    case LoadDataStatus.success:\n                      if (state.currentCategories.isEmpty) return Empty();\n                      return _buildList(context, state.currentCategories);\n                    default:\n                      return PageError(onTap: () { BlocProvider.of<CategoryTreeBloc>(context).add(IncomeCategoryTreeRefreshed()); });\n                  }\n                },\n              ),\n            );\n          },\n        )\n      ),\n    );\n  }\n\n  Widget _buildList(BuildContext context, List<CategoryTree> categories) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      itemCount: categories.length,\n      itemBuilder: (context, index) {\n        CategoryTree categoryTree = categories[index];\n        return ListTile(\n          dense: true,\n          title: Text(categoryTree.name, style: theme.textTheme.bodyText1),\n          subtitle: categoryTree.notes != null && categoryTree.notes!.isNotEmpty ? Text(categoryTree.notes!, style: theme.textTheme.caption) : null,\n          trailing: categoryTree.children != null && categoryTree.children!.isNotEmpty ? Icon(Icons.keyboard_arrow_right) : null,\n          onTap: () {\n            if (categoryTree.children != null && categoryTree.children!.isNotEmpty) {\n              BlocProvider.of<CategoryTreeBloc>(context).add(CategoryItemClicked(categoryTree: categoryTree));\n            } else {\n              Navigator.pushNamed(context, '/category-detail', arguments: CategoryDetailArguments(category: Category.fromTree(categoryTree), categoryType: 2));\n            }\n          },\n          onLongPress: () {\n            Navigator.pushNamed(context, '/category-detail', arguments: CategoryDetailArguments(category: Category.fromTree(categoryTree), categoryType: 2));\n          },\n        );\n      },\n      separatorBuilder: (context, index) {\n        return Divider();\n      },\n    );\n  }\n\n  Future<bool> _onWillPop(state) async {\n    if (state.currentLevel == 1) return true;\n    BlocProvider.of<CategoryTreeBloc>(context).add(CategoryBackClicked());\n    return false;\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/income_category_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/categories/categories.dart';\n\nclass IncomeCategoryFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final Category? category;\n\n  const IncomeCategoryFormPage({\n    required this.type,\n    this.category\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        centerTitle: true,\n        title: Text(_buildTitle(type)),\n        actions: [\n          IconButton(\n            icon: Icon(Icons.done),\n            onPressed: () {\n              BlocProvider.of<CategoryFormBloc>(context).add(CategoryFormSubmitted(type, 2, category));\n            }\n          )\n        ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              IncomeCategoryInput(),\n              NameInput(),\n              SizedBox(height: 10),\n              NotesInput(),\n              SizedBox(height: 10),\n            ],\n          ),\n        ),\n      )\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增收入分类';\n      case 2:\n        return '修改收入分类';\n      default:\n        return '操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/widgets/category_form/expense_category_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\nimport '/commons/commons.dart';\nimport '/categories/categories.dart';\n\n\nclass ExpenseCategoryInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<ExpenseCategorySelectBloc>().state;\n    return BlocBuilder<CategoryFormBloc, CategoryFormState>(\n      buildWhen: (previous, current) => previous.request.parentId != current.request.parentId,\n      builder: (context, state) {\n        return SmartSelect<String>.single\n          (\n            title: '父级分类',\n            selectedValue: state.request.parentId ?? '',\n            onChange: (selected) {\n              context.read<CategoryFormBloc>().add(CategoryFormParentChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is ExpenseCategorySelectStateLoadSuccess ? modelToChoice(state1.categories) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is ExpenseCategorySelectStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n        );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/widgets/category_form/income_category_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\nimport '/commons/commons.dart';\nimport '/categories/categories.dart';\n\nclass IncomeCategoryInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<IncomeCategorySelectBloc>().state;\n    return BlocBuilder<CategoryFormBloc, CategoryFormState>(\n      buildWhen: (previous, current) => previous.request.parentId != current.request.parentId,\n      builder: (context, state) {\n        return SmartSelect<String>.single\n          (\n            title: '父级分类',\n            selectedValue: state.request.parentId ?? '',\n            onChange: (selected) {\n              context.read<CategoryFormBloc>().add(CategoryFormParentChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is IncomeCategorySelectStateLoadSuccess ? modelToChoice(state1.categories) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is IncomeCategorySelectStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n        );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/widgets/category_form/name_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/categories/categories.dart';\n\nclass NameInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n        '名称',\n        BlocBuilder<CategoryFormBloc, CategoryFormState>(\n          buildWhen: (previous, current) => previous.request.name != current.request.name,\n          builder: (context, state) {\n            controller.value = TextEditingValue(\n              text: state.request.name ?? '',\n              selection: TextSelection.fromPosition(\n                TextPosition(offset: state.request.name?.length ?? 0),\n              ),\n            );\n            return\n              TextField(\n                controller: controller,\n                onChanged: (value) => context.read<CategoryFormBloc>().add(CategoryFormNameChanged(value)),\n                decoration: InputDecoration(\n                  contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                ),\n              );\n          }\n        ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/categories/ui/widgets/category_form/notes_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/categories/categories.dart';\nimport '/commons/commons.dart';\n\nclass NotesInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n          '备注',\n          BlocBuilder<CategoryFormBloc, CategoryFormState>(\n              buildWhen: (previous, current) => previous.request.notes != current.request.notes,\n              builder: (context, state) {\n                controller.value = TextEditingValue(\n                  text: state.request.notes ?? '',\n                  selection: TextSelection.fromPosition(\n                    TextPosition(offset: state.request.notes?.length ?? 0),\n                  ),\n                );\n                return\n                  TextField(\n                    controller: controller,\n                    onChanged: (value) => context.read<CategoryFormBloc>().add(CategoryFormNotesChanged(value)),\n                    decoration: InputDecoration(),\n                  );\n              }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_asset/report_asset_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/charts/charts.dart';\n\npart 'report_asset_state.dart';\npart 'report_asset_event.dart';\n\nclass ReportAssetBloc extends Bloc<ReportAssetEvent, ReportAssetState> {\n\n  final ReportRepository reportRepository;\n\n  ReportAssetBloc({\n    required this.reportRepository\n  }) : super(ReportAssetStateLoadInProgress()) {\n    on<ReportAssetLoaded>(_onReportAssetLoaded);\n  }\n\n  void _onReportAssetLoaded(_, Emitter<ReportAssetState> emit) async {\n    try {\n      emit(ReportAssetStateLoadInProgress());\n      final xys = await reportRepository.getAsset();\n      emit(ReportAssetStateLoadSuccess(xys));\n    } catch (_) {\n      emit(ReportAssetStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_asset/report_asset_event.dart",
    "content": "part of 'report_asset_bloc.dart';\n\nabstract class ReportAssetEvent extends Equatable {\n  const ReportAssetEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass ReportAssetLoaded extends ReportAssetEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_asset/report_asset_state.dart",
    "content": "part of 'report_asset_bloc.dart';\n\nabstract class ReportAssetState extends Equatable {\n  const ReportAssetState();\n  @override\n  List<Object> get props => [];\n}\n\nclass ReportAssetStateLoadInProgress extends ReportAssetState { }\n\nclass ReportAssetStateLoadSuccess extends ReportAssetState {\n  final List<XY> xys;\n  const ReportAssetStateLoadSuccess(this.xys);\n  @override\n  List<Object> get props => [xys];\n  num get total {\n    num total = xys.fold(0, (previousValue, element) => previousValue + element.y*100);\n    return total / 100;\n  }\n}\n\nclass ReportAssetStateLoadFailure extends ReportAssetState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_debt/report_debt_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/charts/charts.dart';\n\npart 'report_debt_state.dart';\npart 'report_debt_event.dart';\n\nclass ReportDebtBloc extends Bloc<ReportDebtEvent, ReportDebtState> {\n\n  final ReportRepository reportRepository;\n\n  ReportDebtBloc({\n    required this.reportRepository\n  }) : super(ReportDebtStateLoadInProgress()) {\n    on<ReportDebtLoaded>(_onReportDebtLoaded);\n  }\n\n  void _onReportDebtLoaded(_, Emitter<ReportDebtState> emit) async {\n    try {\n      emit(ReportDebtStateLoadInProgress());\n      final xys = await reportRepository.getDebt();\n      emit(ReportDebtStateLoadSuccess(xys));\n    } catch (_) {\n      emit(ReportDebtStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_debt/report_debt_event.dart",
    "content": "part of 'report_debt_bloc.dart';\n\nabstract class ReportDebtEvent extends Equatable {\n  const ReportDebtEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass ReportDebtLoaded extends ReportDebtEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_debt/report_debt_state.dart",
    "content": "part of 'report_debt_bloc.dart';\n\nabstract class ReportDebtState extends Equatable {\n  const ReportDebtState();\n  @override\n  List<Object> get props => [];\n}\n\nclass ReportDebtStateLoadInProgress extends ReportDebtState { }\n\nclass ReportDebtStateLoadSuccess extends ReportDebtState {\n  final List<XY> xys;\n  const ReportDebtStateLoadSuccess(this.xys);\n  @override\n  List<Object> get props => [xys];\n  num get total {\n    num total = xys.fold(0, (previousValue, element) => previousValue + element.y*100);\n    // 解决精度问题\n    return total / 100;\n  }\n}\n\nclass ReportDebtStateLoadFailure extends ReportDebtState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_expense_category/report_expense_category_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\nimport '/commons/commons.dart';\nimport '/charts/charts.dart';\n\npart 'report_expense_category_event.dart';\npart 'report_expense_category_state.dart';\n\nclass ReportExpenseCategoryBloc extends Bloc<ReportExpenseCategoryEvent, ReportExpenseCategoryState> {\n\n  final ReportRepository reportRepository;\n\n  ReportExpenseCategoryBloc({\n    required this.reportRepository\n  }) : super(ReportExpenseCategoryState()) {\n    on<ReportExpenseCategoryRefreshed>(_onRefreshed);\n    on<ReportExpenseCategoryReset>(_onReset);\n    on<ReportExpenseCategoryMinTimeChanged>(_onMinTimeChanged);\n    on<ReportExpenseCategoryMaxTimeChanged>(_onMaxTimeChanged);\n    on<ReportExpenseCategoryCategoryChanged>(_onCategoryChanged);\n  }\n\n  void _onRefreshed(_, Emitter<ReportExpenseCategoryState> emit) async {\n    try {\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n        request: state.request.copyWith(\n          minTime: state.request.minTime ?? DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch,\n          maxTime: state.request.maxTime ?? DateTime.now().millisecondsSinceEpoch,\n        )\n      ));\n      final xys = await reportRepository.queryExpenseCategory(state.request);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        xys: xys,\n      ));\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onMinTimeChanged(ReportExpenseCategoryMinTimeChanged event, Emitter<ReportExpenseCategoryState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(minTime: event.minTime),\n    ));\n  }\n\n  void _onMaxTimeChanged(ReportExpenseCategoryMaxTimeChanged event, Emitter<ReportExpenseCategoryState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(maxTime: event.maxTime),\n    ));\n  }\n\n  void _onCategoryChanged(ReportExpenseCategoryCategoryChanged event, Emitter<ReportExpenseCategoryState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(categoryId: event.categoryId),\n    ));\n  }\n\n  void _onReset(_, Emitter<ReportExpenseCategoryState> emit) async {\n    emit(state.copyWith(request: CategoryQueryRequest(\n      minTime: DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch,\n      maxTime: DateTime.now().millisecondsSinceEpoch\n    )));\n    this.add(ReportExpenseCategoryRefreshed());\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_expense_category/report_expense_category_event.dart",
    "content": "part of 'report_expense_category_bloc.dart';\n\n@immutable\nabstract class ReportExpenseCategoryEvent extends Equatable {\n  const ReportExpenseCategoryEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass ReportExpenseCategoryRefreshed extends ReportExpenseCategoryEvent { }\n\nclass ReportExpenseCategoryReset extends ReportExpenseCategoryEvent { }\n\nclass ReportExpenseCategoryMinTimeChanged extends ReportExpenseCategoryEvent {\n  const ReportExpenseCategoryMinTimeChanged(this.minTime);\n  final int minTime;\n  @override\n  List<Object> get props => [minTime];\n}\n\nclass ReportExpenseCategoryMaxTimeChanged extends ReportExpenseCategoryEvent {\n  const ReportExpenseCategoryMaxTimeChanged(this.maxTime);\n  final int maxTime;\n  @override\n  List<Object> get props => [maxTime];\n}\n\nclass ReportExpenseCategoryCategoryChanged extends ReportExpenseCategoryEvent {\n  const ReportExpenseCategoryCategoryChanged(this.categoryId);\n  final String categoryId;\n  @override\n  List<Object> get props => [categoryId];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_expense_category/report_expense_category_state.dart",
    "content": "part of 'report_expense_category_bloc.dart';\n\nclass ReportExpenseCategoryState extends Equatable {\n\n  final LoadDataStatus status;\n  final List<XY> xys;\n  final CategoryQueryRequest request;\n\n  const ReportExpenseCategoryState({\n    this.status = LoadDataStatus.initial,\n    this.xys = const <XY>[],\n    this.request = const CategoryQueryRequest(),\n  });\n\n  ReportExpenseCategoryState copyWith({\n    LoadDataStatus? status,\n    List<XY>? xys,\n    CategoryQueryRequest? request,\n  }) {\n    return ReportExpenseCategoryState(\n      status: status ?? this.status,\n      xys: xys ?? this.xys,\n      request: request ?? this.request,\n    );\n  }\n\n  num get total {\n    if (status == LoadDataStatus.success) {\n      num total = xys.fold(0, (previousValue, element) => previousValue + element.y);\n      return num.parse(total.toStringAsFixed(2));\n    } else {\n      return 0;\n    }\n  }\n\n  @override\n  List<Object> get props => [status, xys, request];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_income_category/report_income_category_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\nimport '/commons/commons.dart';\nimport '/charts/charts.dart';\n\npart 'report_income_category_event.dart';\npart 'report_income_category_state.dart';\n\nclass ReportIncomeCategoryBloc extends Bloc<ReportIncomeCategoryEvent, ReportIncomeCategoryState> {\n\n  final ReportRepository reportRepository;\n\n  ReportIncomeCategoryBloc({\n    required this.reportRepository\n  }) : super(ReportIncomeCategoryState()) {\n    on<ReportIncomeCategoryRefreshed>(_onRefreshed);\n    on<ReportIncomeCategoryReset>(_onReset);\n    on<ReportIncomeCategoryMinTimeChanged>(_onMinTimeChanged);\n    on<ReportIncomeCategoryMaxTimeChanged>(_onMaxTimeChanged);\n    on<ReportIncomeCategoryCategoryChanged>(_onCategoryChanged);\n  }\n\n  void _onRefreshed(_, Emitter<ReportIncomeCategoryState> emit) async {\n    try {\n      emit(state.copyWith(\n          status: LoadDataStatus.progress,\n          request: state.request.copyWith(\n            minTime: state.request.minTime ?? DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch,\n            maxTime: state.request.maxTime ?? DateTime.now().millisecondsSinceEpoch,\n          )\n      ));\n      final xys = await reportRepository.queryIncomeCategory(state.request);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        xys: xys,\n      ));\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onMinTimeChanged(ReportIncomeCategoryMinTimeChanged event, Emitter<ReportIncomeCategoryState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(minTime: event.minTime),\n    ));\n  }\n\n  void _onMaxTimeChanged(ReportIncomeCategoryMaxTimeChanged event, Emitter<ReportIncomeCategoryState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(maxTime: event.maxTime),\n    ));\n  }\n\n  void _onCategoryChanged(ReportIncomeCategoryCategoryChanged event, Emitter<ReportIncomeCategoryState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(categoryId: event.categoryId),\n    ));\n  }\n\n  void _onReset(_, Emitter<ReportIncomeCategoryState> emit) async {\n    emit(state.copyWith(request: CategoryQueryRequest(\n        minTime: DateTime.now().subtract(const Duration(days: 30)).millisecondsSinceEpoch,\n        maxTime: DateTime.now().millisecondsSinceEpoch\n    )));\n    this.add(ReportIncomeCategoryRefreshed());\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_income_category/report_income_category_event.dart",
    "content": "part of 'report_income_category_bloc.dart';\n\n@immutable\nabstract class ReportIncomeCategoryEvent extends Equatable {\n  const ReportIncomeCategoryEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass ReportIncomeCategoryRefreshed extends ReportIncomeCategoryEvent { }\n\nclass ReportIncomeCategoryReset extends ReportIncomeCategoryEvent { }\n\nclass ReportIncomeCategoryMinTimeChanged extends ReportIncomeCategoryEvent {\n  const ReportIncomeCategoryMinTimeChanged(this.minTime);\n  final int minTime;\n  @override\n  List<Object> get props => [minTime];\n}\n\nclass ReportIncomeCategoryMaxTimeChanged extends ReportIncomeCategoryEvent {\n  const ReportIncomeCategoryMaxTimeChanged(this.maxTime);\n  final int maxTime;\n  @override\n  List<Object> get props => [maxTime];\n}\n\nclass ReportIncomeCategoryCategoryChanged extends ReportIncomeCategoryEvent {\n  const ReportIncomeCategoryCategoryChanged(this.categoryId);\n  final String categoryId;\n  @override\n  List<Object> get props => [categoryId];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/bloc/report_income_category/report_income_category_state.dart",
    "content": "part of 'report_income_category_bloc.dart';\n\nclass ReportIncomeCategoryState extends Equatable {\n\n  final LoadDataStatus status;\n  final List<XY> xys;\n  final CategoryQueryRequest request;\n\n  const ReportIncomeCategoryState({\n    this.status = LoadDataStatus.initial,\n    this.xys = const <XY>[],\n    this.request = const CategoryQueryRequest(),\n  });\n\n  ReportIncomeCategoryState copyWith({\n    LoadDataStatus? status,\n    List<XY>? xys,\n    CategoryQueryRequest? request,\n  }) {\n    return ReportIncomeCategoryState(\n      status: status ?? this.status,\n      xys: xys ?? this.xys,\n      request: request ?? this.request,\n    );\n  }\n\n  num get total {\n    if (status == LoadDataStatus.success) {\n      num total = xys.fold(0, (previousValue, element) => previousValue + element.y);\n      return num.parse(total.toStringAsFixed(2));\n    } else {\n      return 0;\n    }\n  }\n\n  @override\n  List<Object> get props => [status, xys, request];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/charts.dart",
    "content": "export 'data/report_repository.dart';\nexport 'data/models/x_y.dart';\nexport 'data/models/category_query_request.dart';\nexport 'bloc/report_asset/report_asset_bloc.dart';\nexport 'bloc/report_debt/report_debt_bloc.dart';\nexport 'bloc/report_expense_category/report_expense_category_bloc.dart';\nexport 'bloc/report_income_category/report_income_category_bloc.dart';\nexport 'ui/charts_page.dart';\nexport 'ui/charts_expense_category_filter_page.dart';\nexport 'ui/charts_income_category_filter_page.dart';\nexport 'ui/widgets/asset_sheet.dart';\nexport 'ui/widgets/debt_sheet.dart';\nexport 'ui/widgets/expense_category.dart';\nexport 'ui/widgets/income_category.dart';\nexport 'ui/widgets/circular_legend.dart';\nexport 'ui/widgets/date_input_expense.dart';\nexport 'ui/widgets/date_input_income.dart';\nexport 'ui/widgets/expense_category_input.dart';\nexport 'ui/widgets/income_category_input.dart';\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/data/models/category_query_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'category_query_request.g.dart';\n\n@JsonSerializable()\nclass CategoryQueryRequest extends Equatable {\n\n  final int? minTime;\n  final int? maxTime;\n  final String? categoryId;\n\n  const CategoryQueryRequest({\n    this.minTime,\n    this.maxTime,\n    this.categoryId\n  });\n\n  factory CategoryQueryRequest.fromJson(Map<String, dynamic> json) => _$CategoryQueryRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$CategoryQueryRequestToJson(this);\n\n  @override\n  List<Object?> get props => [minTime, maxTime, categoryId];\n\n  CategoryQueryRequest copyWith({\n    int? minTime,\n    int? maxTime,\n    String? categoryId\n  }) {\n    return CategoryQueryRequest(\n      minTime: minTime ?? this.minTime,\n      maxTime: maxTime ?? this.maxTime,\n      categoryId: categoryId ?? this.categoryId\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/data/models/category_query_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'category_query_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nCategoryQueryRequest _$CategoryQueryRequestFromJson(\n        Map<String, dynamic> json) =>\n    CategoryQueryRequest(\n      minTime: json['minTime'] as int?,\n      maxTime: json['maxTime'] as int?,\n      categoryId: json['categoryId'] as String?,\n    );\n\nMap<String, dynamic> _$CategoryQueryRequestToJson(\n        CategoryQueryRequest instance) =>\n    <String, dynamic>{\n      'minTime': instance.minTime,\n      'maxTime': instance.maxTime,\n      'categoryId': instance.categoryId,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/data/models/x_y.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'x_y.g.dart';\n@JsonSerializable()\nclass XY extends Equatable {\n\n  final String x;\n  final num y;\n  final num percent;\n\n  XY({\n    required this.x,\n    required this.y,\n    required this.percent\n  });\n\n  factory XY.fromJson(Map<String, dynamic> json) => _$XYFromJson(json);\n\n  Map<String, dynamic> toJson() => _$XYToJson(this);\n\n  @override\n  List<Object> get props => [x, y];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/data/models/x_y.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'x_y.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nXY _$XYFromJson(Map<String, dynamic> json) => XY(\n      x: json['x'] as String,\n      y: json['y'] as num,\n      percent: json['percent'] as num,\n    );\n\nMap<String, dynamic> _$XYToJson(XY instance) => <String, dynamic>{\n      'x': instance.x,\n      'y': instance.y,\n      'percent': instance.percent,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/data/report_repository.dart",
    "content": "import 'dart:convert';\nimport '/commons/commons.dart';\nimport '/charts/charts.dart';\n\nclass ReportRepository {\n\n  List<XY> _responseToList(String response) {\n    return (json.decode(response)['data']).map<XY>((i) => XY.fromJson(i)).toList();\n  }\n\n  Future<List<XY>> getAsset() async {\n    String response = await HttpClient().get('reports/asset');\n    return _responseToList(response);\n  }\n\n  Future<List<XY>> getDebt() async {\n    String response = await HttpClient().get('reports/debt');\n    return _responseToList(response);\n  }\n\n  Future<List<XY>> queryExpenseCategory(CategoryQueryRequest request) async {\n    String response = await HttpClient().get('reports/expense-category', params: request.toJson());\n    return _responseToList(response);\n  }\n\n  Future<List<XY>> queryIncomeCategory(CategoryQueryRequest request) async {\n    String response = await HttpClient().get('reports/income-category', params: request.toJson());\n    return _responseToList(response);\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/charts_expense_category_filter_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/charts/charts.dart';\n\nclass ChartsExpenseCategoryFilterPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('搜索'),\n        actions: [\n          IconButton(\n            icon: Icon(Icons.done),\n            onPressed: () {\n              BlocProvider.of<ReportExpenseCategoryBloc>(context).add(ReportExpenseCategoryRefreshed());\n              Navigator.pop(context);\n            },\n          ),\n        ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              DateInputExpense(),\n              ExpenseCategoryInput()\n            ],\n          ),\n        )\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/charts_income_category_filter_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/charts/charts.dart';\n\nclass ChartsIncomeCategoryFilterPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('搜索'),\n        actions: [\n          IconButton(\n            icon: Icon(Icons.done),\n            onPressed: () {\n              BlocProvider.of<ReportIncomeCategoryBloc>(context).add(ReportIncomeCategoryRefreshed());\n              Navigator.pop(context);\n            },\n          ),\n        ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              DateInputIncome(),\n              IncomeCategoryInput()\n            ],\n          ),\n        )\n      ),\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/charts_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/charts/charts.dart';\n\nclass ChartsPage extends StatefulWidget {\n  @override\n  State<ChartsPage> createState() => _ChartsPageState();\n}\n\nclass _ChartsPageState extends State<ChartsPage> with TickerProviderStateMixin {\n\n  int tabIndex = 0;\n\n  @override\n  Widget build(BuildContext context) {\n    return DefaultTabController(\n      length: 4,\n      initialIndex: 0,\n      child: Builder(\n        builder: (context) {\n          final tabController = DefaultTabController.of(context)!;\n          tabController.addListener(() {\n            setState(() {\n              tabIndex = tabController.index;\n            });\n          });\n          return Scaffold(\n              appBar: AppBar(\n                leading: IconButton(\n                    onPressed: () {\n                      switch (tabIndex) {\n                        case 0:\n                          BlocProvider.of<ReportExpenseCategoryBloc>(context).add(ReportExpenseCategoryReset());\n                          break;\n                        case 1:\n                          BlocProvider.of<ReportIncomeCategoryBloc>(context).add(ReportIncomeCategoryReset());\n                          break;\n                        case 2:\n                          BlocProvider.of<ReportAssetBloc>(context).add(ReportAssetLoaded());\n                          break;\n                        case 3:\n                          BlocProvider.of<ReportDebtBloc>(context).add(ReportDebtLoaded());\n                          break;\n                      }\n                    },\n                    icon: const Icon(Icons.refresh)\n                ),\n                title: TabBar(\n                  labelPadding: EdgeInsets.all(0),\n                  tabs: [\n                    Tab(child: Text('支出')),\n                    Tab(child: Text('收入')),\n                    Tab(child: Text('资产')),\n                    Tab(child: Text('负债')),\n                  ],\n                ),\n                actions: [\n                  IconButton(\n                      onPressed: (tabIndex == 2 || tabIndex == 3) ? null : () {\n                        if (tabIndex == 0) {\n                          Navigator.pushNamed(context, '/charts-expense-category-filter');\n                        } else if (tabIndex == 1) {\n                          Navigator.pushNamed(context, '/charts-income-category-filter');\n                        }\n                      },\n                      icon: const Icon(Icons.search)\n                  )\n                ],\n              ),\n              body: TabBarView(\n                  children: [\n                    ExpenseCategory(),\n                    IncomeCategory(),\n                    AssetSheet(),\n                    DebtSheet(),\n                  ]\n              )\n          );\n        },\n      ),\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/asset_sheet.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:syncfusion_flutter_charts/charts.dart';\nimport '/charts/charts.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\n\nclass AssetSheet extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return SingleChildScrollView(\n        child: Column(\n          children: [\n            BlocBuilder<ReportAssetBloc, ReportAssetState>(\n              builder: (context, state) {\n                if (state is ReportAssetStateLoadSuccess) {\n                  return Column(\n                    children: [\n                      SfCircularChart(\n                        title: ChartTitle(text: '资产分类'),\n                        legend: Legend(isVisible: false),\n                        tooltipBehavior: TooltipBehavior(enable: true),\n                        series: getDefaultDoughnutSeries(state.xys),\n                        annotations: [\n                          CircularChartAnnotation(\n                            widget: Column(\n                              mainAxisAlignment: MainAxisAlignment.center,\n                              crossAxisAlignment: CrossAxisAlignment.center,\n                              children: [\n                                Text('总金额', style: TextStyle(color: Colors.grey, fontSize: 12)),\n                                Text(removeDecimalZero(state.total), style: TextStyle(color: Colors.black, fontSize: 18))\n                              ],\n                            )\n                          )\n                        ],\n                      ),\n                      Divider(),\n                      CircularLegend(xys: state.xys),\n                      Divider(),\n                    ],\n                  );\n                }\n                return Container(\n                  height: 200,\n                  child: const PageLoading(),\n                );\n              },\n            ),\n          ],\n        )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/circular_legend.dart",
    "content": "import 'package:flutter/material.dart';\nimport '/commons/commons.dart';\nimport '/charts/charts.dart';\n\nclass CircularLegend extends StatelessWidget {\n\n  final List<XY> xys;\n\n  CircularLegend({\n    required this.xys\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      shrinkWrap: true,\n      physics: NeverScrollableScrollPhysics(),\n      itemCount: xys.length,\n      separatorBuilder: (_, __) => const Divider(),\n      itemBuilder: (context, index) {\n        XY xy = xys[index];\n        return ListTile(\n          dense: true,\n          visualDensity: VisualDensity(horizontal: 0, vertical: -4),\n          title: Text(xy.x, style: theme.textTheme.titleSmall),\n          subtitle: Text(removeDecimalZero(xy.y), style: theme.textTheme.caption),\n          trailing: Text(removeDecimalZero(xy.percent)+\"%\", style: theme.textTheme.titleMedium),\n        );\n      }\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/date_input_expense.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:syncfusion_flutter_datepicker/datepicker.dart';\nimport 'package:intl/intl.dart';\nimport '/components/components.dart';\nimport '/charts/charts.dart';\n\nclass DateInputExpense extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<ReportExpenseCategoryBloc, ReportExpenseCategoryState>(\n      buildWhen: (previous, current) => previous.request.minTime != current.request.minTime || previous.request.maxTime != current.request.maxTime,\n      builder: (context, state) {\n        return Row(\n          children: [\n            ElevatedButton(\n                onPressed: () async {\n                  final PickerDateRange? range =\n                  await showDialog(\n                      context: context,\n                      builder: (BuildContext context) {\n                        return DateRangePicker(\n                            PickerDateRange(DateTime.fromMillisecondsSinceEpoch(state.request.minTime!), DateTime.fromMillisecondsSinceEpoch(state.request.maxTime!))\n                        );\n                      }\n                  );\n                  if (range != null) {\n                    BlocProvider.of<ReportExpenseCategoryBloc>(context).add(ReportExpenseCategoryMinTimeChanged(range.startDate!.millisecondsSinceEpoch));\n                    BlocProvider.of<ReportExpenseCategoryBloc>(context).add(ReportExpenseCategoryMaxTimeChanged(range.endDate!.millisecondsSinceEpoch));\n                  }\n                },\n                child: Text('选择日期范围')\n            ),\n            SizedBox(width: 10),\n            Text(\n                DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.minTime!))+\n                    ' - '+\n                    DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.maxTime!))\n            )\n          ],\n        );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/date_input_income.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:syncfusion_flutter_datepicker/datepicker.dart';\nimport 'package:intl/intl.dart';\nimport '/components/components.dart';\nimport '/charts/charts.dart';\n\nclass DateInputIncome extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<ReportIncomeCategoryBloc, ReportIncomeCategoryState>(\n      buildWhen: (previous, current) => previous.request.minTime != current.request.minTime || previous.request.maxTime != current.request.maxTime,\n      builder: (context, state) {\n        return Row(\n          children: [\n            ElevatedButton(\n                onPressed: () async {\n                  final PickerDateRange? range =\n                  await showDialog(\n                      context: context,\n                      builder: (BuildContext context) {\n                        return DateRangePicker(\n                            PickerDateRange(DateTime.fromMillisecondsSinceEpoch(state.request.minTime!), DateTime.fromMillisecondsSinceEpoch(state.request.maxTime!))\n                        );\n                      }\n                  );\n                  if (range != null) {\n                    BlocProvider.of<ReportIncomeCategoryBloc>(context).add(ReportIncomeCategoryMinTimeChanged(range.startDate!.millisecondsSinceEpoch));\n                    BlocProvider.of<ReportIncomeCategoryBloc>(context).add(ReportIncomeCategoryMaxTimeChanged(range.endDate!.millisecondsSinceEpoch));\n                  }\n                },\n                child: Text('选择日期范围')\n            ),\n            SizedBox(width: 10),\n            Text(\n                DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.minTime!))+\n                    ' - '+\n                    DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.maxTime!))\n            )\n          ],\n        );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/debt_sheet.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:syncfusion_flutter_charts/charts.dart';\nimport '/charts/charts.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\n\nclass DebtSheet extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return SingleChildScrollView(\n        child: Column(\n          children: [\n            BlocBuilder<ReportDebtBloc, ReportDebtState>(\n              builder: (context, state) {\n                if (state is ReportDebtStateLoadSuccess) {\n                  return Column(\n                    children: [\n                      SfCircularChart(\n                        title: ChartTitle(text: '负债分类'),\n                        legend: Legend(isVisible: false),\n                        tooltipBehavior: TooltipBehavior(enable: true),\n                        series: getDefaultDoughnutSeries(state.xys),\n                        annotations: [\n                          CircularChartAnnotation(\n                            widget: Column(\n                              mainAxisAlignment: MainAxisAlignment.center,\n                              crossAxisAlignment: CrossAxisAlignment.center,\n                              children: [\n                                Text('总金额', style: TextStyle(color: Colors.grey, fontSize: 12)),\n                                Text(removeDecimalZero(state.total), style: TextStyle(color: Colors.black, fontSize: 18))\n                              ],\n                            )\n                          )\n                        ],\n                      ),\n                      Divider(),\n                      CircularLegend(xys: state.xys),\n                      Divider(),\n                    ],\n                  );\n                }\n                return Container(\n                  height: 200,\n                  child: const PageLoading(),\n                );\n              },\n            ),\n          ],\n        )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/expense_category.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:intl/intl.dart';\nimport 'package:syncfusion_flutter_charts/charts.dart';\nimport '/charts/charts.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\n\nclass ExpenseCategory extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return SingleChildScrollView(\n        child: BlocBuilder<ReportExpenseCategoryBloc, ReportExpenseCategoryState>(\n          builder: (context, state) {\n            if (state.status == LoadDataStatus.success) {\n              return Column(\n                children: [\n                  SizedBox(height: 30),\n                  Text(\n                      DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.minTime!))+\n                          ' - '+\n                          DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.maxTime!)),\n                    style: theme.textTheme.headline6,\n                  ),\n                  SfCircularChart(\n                    title: ChartTitle(text: '支出分类'),\n                    legend: Legend(isVisible: false),\n                    tooltipBehavior: TooltipBehavior(enable: true),\n                    series: getDefaultDoughnutSeries(state.xys),\n                    annotations: [\n                      CircularChartAnnotation(\n                        widget: Column(\n                          mainAxisAlignment: MainAxisAlignment.center,\n                          crossAxisAlignment: CrossAxisAlignment.center,\n                          children: [\n                            Text('总金额', style: TextStyle(color: Colors.grey, fontSize: 12)),\n                            Text(removeDecimalZero(state.total), style: TextStyle(color: Colors.black, fontSize: 18))\n                          ],\n                        )\n                      )\n                    ],\n                  ),\n                  Divider(),\n                  CircularLegend(xys: state.xys),\n                ],\n              );\n            }\n            return Container(\n              height: 200,\n              child: const PageLoading(),\n            );\n          },\n        )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/expense_category_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/charts/charts.dart';\nimport '/categories/categories.dart';\n\nclass ExpenseCategoryInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<ExpenseCategorySelectBloc>().state;\n    return BlocSelector<ReportExpenseCategoryBloc, ReportExpenseCategoryState, String?>(\n      selector: (state) => state.request.categoryId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n          (\n            title: '支出分类',\n            selectedValue: state ?? '',\n            onChange: (selected) {\n              context.read<ReportExpenseCategoryBloc>().add(ReportExpenseCategoryCategoryChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is ExpenseCategorySelectStateLoadSuccess ? modelToChoice(state1.categories) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto:true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is ExpenseCategorySelectStateLoadInProgress,\n                padding: EdgeInsets.symmetric(horizontal: 0),\n              );\n            }\n        );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/income_category.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:intl/intl.dart';\nimport 'package:syncfusion_flutter_charts/charts.dart';\nimport '/charts/charts.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\n\nclass IncomeCategory extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return SingleChildScrollView(\n        child: BlocBuilder<ReportIncomeCategoryBloc, ReportIncomeCategoryState>(\n          builder: (context, state) {\n            if (state.status == LoadDataStatus.success) {\n              return Column(\n                children: [\n                  SizedBox(height: 30),\n                  Text(\n                    DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.minTime!))+\n                        ' - '+\n                        DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.maxTime!)),\n                    style: Theme.of(context).textTheme.headline6,\n                  ),\n                  SfCircularChart(\n                    title: ChartTitle(text: '收入分类'),\n                    legend: Legend(isVisible: false),\n                    tooltipBehavior: TooltipBehavior(enable: true),\n                    series: getDefaultDoughnutSeries(state.xys),\n                    annotations: [\n                      CircularChartAnnotation(\n                        widget: Column(\n                          mainAxisAlignment: MainAxisAlignment.center,\n                          crossAxisAlignment: CrossAxisAlignment.center,\n                          children: [\n                            Text('总金额', style: TextStyle(color: Colors.grey, fontSize: 12)),\n                            Text(removeDecimalZero(state.total), style: TextStyle(color: Colors.black, fontSize: 18))\n                          ],\n                        )\n                      )\n                    ],\n                  ),\n                  Divider(),\n                  CircularLegend(xys: state.xys),\n                ],\n              );\n            }\n            return Container(\n              height: 200,\n              child: const PageLoading(),\n            );\n          },\n        )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/charts/ui/widgets/income_category_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/charts/charts.dart';\nimport '/categories/categories.dart';\n\nclass IncomeCategoryInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<IncomeCategorySelectBloc>().state;\n    return BlocSelector<ReportIncomeCategoryBloc, ReportIncomeCategoryState, String?>(\n      selector: (state) => state.request.categoryId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n            (\n              title: '收入分类',\n              selectedValue: state ?? '',\n              onChange: (selected) {\n                context.read<ReportIncomeCategoryBloc>().add(ReportIncomeCategoryCategoryChanged(selected.value ?? ''));\n              },\n              choiceItems: state1 is IncomeCategorySelectStateLoadSuccess ? modelToChoice(state1.categories) : [],\n              choiceType: S2ChoiceType.chips,\n              modalFilter: true,\n              modalFilterAuto:true,\n              tileBuilder: (context, state) {\n                return S2Tile.fromState(\n                  state,\n                  isLoading: state1 is IncomeCategorySelectStateLoadInProgress,\n                  padding: EdgeInsets.symmetric(horizontal: 0),\n                );\n              }\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/common_util.dart",
    "content": "import 'dart:convert';\nimport 'package:intl/intl.dart';\n\nString removeDecimalZero(num n) {\n  RegExp regex = RegExp(r\"([.]*0)(?!.*\\d)\");\n  return n.toString().replaceAll(regex, \"\");\n}\n\nString boolToString(bool val) {\n  if (val) return '是';\n  else return '否';\n}\n\nString dateFormat(int? timestamp) {\n  if (timestamp == null) return '';\n  return DateFormat('yyyy-MM-dd').format(DateTime.fromMillisecondsSinceEpoch(timestamp));\n}\n\nbool parseResponse(String response) {\n  return json.decode(response)['success'];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/commons.dart",
    "content": "export 'common_util.dart';\nexport 'http_client.dart';\nexport 'session.dart';\nexport 'widget_util.dart';\nexport 'id_name_model.dart';\nexport 'enums.dart';\nexport 'web_view_page.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/enums.dart",
    "content": "enum LoadDataStatus { initial, progress, success, empty, failure }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/http_client.dart",
    "content": "import 'package:dio/dio.dart';\nimport '/commons/commons.dart';\n\nclass HttpClient {\n\n  // 单例模式\n  static final HttpClient _instance = HttpClient._internal();\n  factory HttpClient() => _instance;\n  HttpClient._internal() {\n    init();\n  }\n\n  late Dio _dio;\n  init(){\n    print('init');\n    print(session['apiUrl']);\n    BaseOptions baseOptions = BaseOptions(\n      // baseUrl: 'http://127.0.0.1:9092/api/v1/',\n      baseUrl: session['apiUrl'],\n      contentType: 'application/json',\n        // responseType: ResponseType.plain,\n      connectTimeout: 30000,\n      receiveTimeout: 30000\n    );\n    _dio = Dio(baseOptions);\n    _dio.interceptors.add(TokenInterceptor());\n    _dio.interceptors.add(ExceptionInterceptor());\n  }\n\n  Future<String> get(String uri, {Map<String, dynamic>? params}) async {\n    var response = await _dio.get(uri, queryParameters: params);\n    return response.toString();\n  }\n\n  Future<String> post(String uri, {data}) async {\n    var response = await _dio.post(uri, data: data);\n    return response.toString();\n  }\n\n  Future<String> delete(String uri) async {\n    var response = await _dio.delete(uri);\n    return response.toString();\n  }\n\n  Future<String> put(String uri, {data}) async {\n    var response = await _dio.put(uri, data: data);\n    return response.toString();\n  }\n\n  Future<String> upload(data) async {\n    var response = await _dio.post('http://upload-z2.qiniup.com', data: FormData.fromMap(data));\n    return response.toString();\n  }\n\n}\n\nclass TokenInterceptor extends Interceptor {\n  @override\n  void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {\n    if (session[\"userToken\"] != null) {\n      options.headers['User-Token'] = session[\"userToken\"];\n    }\n    super.onRequest(options, handler);\n  }\n}\n\n// 全局错误处理\nclass ExceptionInterceptor extends Interceptor {\n  @override\n  void onResponse(Response response, ResponseInterceptorHandler handler) async {\n    if (!response.data['success']) {\n      if (response.data['errorCode'] == 8 || response.data['errorCode'] == 10) {\n        Message.error(\"登录状态已过期，请退出之后重新登录。\");\n      } else {\n        Message.error(response.data['errorMsg']);\n      }\n    }\n    super.onResponse(response, handler);\n  }\n  @override\n  void onError(DioError e, handler) {\n    // if (e.response != null) {\n    //   // The request was made and the server responded with a status code\n    //   // that falls out of the range of 2xx and is also not 304.\n    //   Message.error(e.response!.data['errorMsg']);\n    // } else {\n    //   // Something happened in setting up or sending the request that triggered an Error\n    //   Message.error('网络错误，请稍后重试');\n    // }\n    print(e);\n    String errorMsg = e.response?.data['errorMsg'] ?? '网络错误，请稍后重试';\n    Message.error(errorMsg);\n    super.onError(e, handler);\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/id_name_model.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'id_name_model.g.dart';\n\n@JsonSerializable()\nclass IdNameModel extends Equatable {\n\n  final int id;\n  final String name;\n\n  const IdNameModel({\n    required this.id,\n    required this.name,\n  });\n\n  factory IdNameModel.fromJson(Map<String, dynamic> json) => _$IdNameModelFromJson(json);\n\n  Map<String, dynamic> toJson() => _$IdNameModelToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/id_name_model.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'id_name_model.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nIdNameModel _$IdNameModelFromJson(Map<String, dynamic> json) => IdNameModel(\n      id: json['id'] as int,\n      name: json['name'] as String,\n    );\n\nMap<String, dynamic> _$IdNameModelToJson(IdNameModel instance) =>\n    <String, dynamic>{\n      'id': instance.id,\n      'name': instance.name,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/session.dart",
    "content": "Map<String, dynamic> session = {\n  'userToken': null,\n  'user': null,\n  'apiUrl': 'http://jz.jiukuaitech.com/api/v1/',\n};"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/web_view_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:webview_flutter/webview_flutter.dart';\n\nclass WebViewPage extends StatefulWidget {\n\n  final String title;\n  final String url;\n\n  const WebViewPage({\n    required this.title,\n    required this.url\n  });\n\n  @override\n  _WebViewPageState createState() => _WebViewPageState();\n\n}\n\nclass _WebViewPageState extends State<WebViewPage> {\n\n  int loadingPercentage = 0;\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        centerTitle: true,\n        title: Text(widget.title),\n      ),\n      body: Stack(\n        children: [\n          WebView(\n            initialUrl: widget.url,\n            javascriptMode: JavascriptMode.unrestricted,\n            onPageStarted: (url) {\n              setState(() {\n                loadingPercentage = 0;\n              });\n            },\n            onProgress: (progress) {\n              setState(() {\n                loadingPercentage = progress;\n              });\n            },\n            onPageFinished: (url) {\n              setState(() {\n                loadingPercentage = 100;\n              });\n            },\n          ),\n          if (loadingPercentage < 100) LinearProgressIndicator(value: loadingPercentage / 100.0),\n        ],\n      ),\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/commons/widget_util.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:fluttertoast/fluttertoast.dart';\nimport 'package:awesome_select/awesome_select.dart';\nimport 'package:syncfusion_flutter_charts/charts.dart';\nimport '/charts/charts.dart';\n\nWidget buildFormItem(String label, Widget field, BuildContext context) {\n  return\n    Container(\n      // height: 45,\n      child:\n        Row(\n          mainAxisAlignment: MainAxisAlignment.spaceBetween,\n          crossAxisAlignment: CrossAxisAlignment.center,\n          children: [\n            Text(label, style: Theme.of(context).textTheme.bodyText1),\n            SizedBox(width: 10),\n            Expanded(child: field)\n          ]\n        )\n  );\n}\n\nList<S2Choice<String>> modelToChoice(models) {\n  return S2Choice.listFrom<String, dynamic>(\n    source: models,\n    value: (index, item) => item.id.toString(),\n    title: (index, item) => item.name,\n  );\n}\n\nList<S2Choice<String>> modelToChoiceCode(models) {\n  return S2Choice.listFrom<String, dynamic>(\n    source: models,\n    value: (index, item) => item.code,\n    title: (index, item) => item.code,\n  );\n}\n\nFuture<bool> confirm(\n    BuildContext context, {\n      Widget? title,\n      Widget? content,\n      Widget? textOK,\n      Widget? textCancel,\n    }) async {\n  final bool? isConfirm = await showDialog<bool>(\n    context: context,\n    builder: (_) => WillPopScope(\n      child: AlertDialog(\n        title: title,\n        content: (content != null) ? content : Text('Are you sure continue?'),\n        actions: <Widget>[\n          TextButton(\n            child: textCancel != null ? textCancel : Text('Cancel'),\n            onPressed: () => Navigator.pop(context, false),\n          ),\n          TextButton(\n            child: textOK != null ? textOK : Text('OK'),\n            onPressed: () => Navigator.pop(context, true),\n          ),\n        ],\n      ),\n      onWillPop: () async {\n        Navigator.pop(context, false);\n        return true;\n      },\n    ),\n  );\n\n  return (isConfirm != null) ? isConfirm : false;\n}\n\nclass Message {\n\n  static success(String msg) {\n    Fluttertoast.showToast(\n        msg: msg,\n        toastLength: Toast.LENGTH_SHORT,\n        gravity: ToastGravity.TOP,\n        timeInSecForIosWeb: 1,\n        backgroundColor: Colors.black,\n        textColor: Colors.white,\n        fontSize: 16.0\n    );\n  }\n\n  static error(String msg) {\n    Fluttertoast.showToast(\n        msg: msg,\n        toastLength: Toast.LENGTH_LONG,\n        gravity: ToastGravity.TOP,\n        timeInSecForIosWeb: 1,\n        backgroundColor: Colors.red,\n        textColor: Colors.white,\n        fontSize: 16.0\n    );\n  }\n\n}\n\nvoid fullDialog(BuildContext context, Widget widget) {\n  Navigator.of(context, rootNavigator: false).push( // ensures fullscreen\n      CupertinoPageRoute(\n          fullscreenDialog: true,\n          builder: (context) => widget\n      )\n  );\n}\n\nList<DoughnutSeries<XY, String>> getDefaultDoughnutSeries(xys) {\n  return [\n    DoughnutSeries<XY, String>(\n      radius: '80%',\n      innerRadius: '70%',\n      dataSource: xys,\n      xValueMapper: (XY data, _) => data.x,\n      yValueMapper: (XY data, _) => data.y,\n      dataLabelMapper: (XY data, _) => data.x + \": \" + data.percent.toString() + \"%\",\n      dataLabelSettings: const DataLabelSettings(\n          isVisible: true,\n          textStyle: const TextStyle(fontSize: 6),\n          labelPosition: ChartDataLabelPosition.outside\n      ),\n      legendIconType: LegendIconType.circle,\n    )\n  ];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/components/components.dart",
    "content": "export 'lazy_indexed_stack.dart';\nexport 'page_loading.dart';\nexport 'page_error.dart';\nexport 'empty.dart';\nexport 'popup_menu.dart';\nexport 'date_time_input.dart';\nexport 'date_range_picker.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/components/date_range_picker.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:intl/intl.dart';\nimport 'package:syncfusion_flutter_datepicker/datepicker.dart';\n\nclass DateRangePicker extends StatefulWidget {\n  final dynamic range;\n  const DateRangePicker(this.range);\n\n  @override\n  _DateRangePickerState createState() => _DateRangePickerState();\n}\n\nclass _DateRangePickerState extends State<DateRangePicker> {\n\n  dynamic _controller;\n  dynamic _range;\n\n  @override\n  void initState() {\n    _range = widget.range;\n    _controller = DateRangePickerController();\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n\n    final Widget selectedDateWidget = Container(\n      color: Colors.transparent,\n      padding: const EdgeInsets.symmetric(vertical: 16.0),\n      child: Container(\n        height: 30,\n        padding: const EdgeInsets.symmetric(horizontal: 4.0),\n        child: _range == null || _range.startDate == null || _range.endDate == null || _range.startDate == _range.endDate ?\n          Text(\n            DateFormat('yyyy-MM-dd').format(_range.startDate ?? _range.endDate),\n            textAlign: TextAlign.center,\n            style: TextStyle(\n              fontSize: 18,\n              fontWeight: FontWeight.w600,\n              color: Colors.black\n            )\n          ) :\n        Row(\n          children: [\n            Expanded(\n                flex: 5,\n                child: Text(\n                  DateFormat('yyyy-MM-dd').format(\n                    _range.startDate.isAfter(_range.endDate) ==\n                        true\n                        ? _range.endDate\n                        : _range.startDate\n                  ),\n                  maxLines: 1,\n                  overflow: TextOverflow.ellipsis,\n                  textAlign: TextAlign.center,\n                  style: TextStyle(\n                      fontSize: 18,\n                      fontWeight: FontWeight.w600,\n                      color: Colors.black),\n                )\n            ),\n            const VerticalDivider(\n              thickness: 1,\n            ),\n            Expanded(\n                flex: 5,\n                child: Text(\n                  DateFormat('yyyy-MM-dd').format(\n                    _range.startDate.isAfter(_range.endDate) ==\n                        true\n                        ? _range.startDate\n                        : _range.endDate\n                  ),\n                  maxLines: 1,\n                  overflow: TextOverflow.ellipsis,\n                  textAlign: TextAlign.center,\n                  style: TextStyle(\n                      fontSize: 18,\n                      fontWeight: FontWeight.w600,\n                      color: Colors.black),\n                )\n            )\n          ],\n        ),\n      ),\n    );\n\n    _controller.selectedRange = _range;\n\n    Widget pickerWidget = SfDateRangePicker(\n      controller: _controller,\n      selectionMode: DateRangePickerSelectionMode.range,\n      showNavigationArrow: true,\n      showActionButtons: true,\n      confirmText: '确定',\n      cancelText: '取消',\n      onCancel: () => Navigator.pop(context, null),\n      headerStyle: DateRangePickerHeaderStyle(\n          textAlign: TextAlign.center,\n          textStyle: TextStyle(color: Colors.black, fontSize: 15)\n      ),\n      onSubmit: (Object? value) {\n        Navigator.pop(context, _range);\n      },\n      onSelectionChanged: (DateRangePickerSelectionChangedArgs details) {\n        setState(() {\n          _range = details.value;\n        });\n      },\n    );\n\n    return Dialog(\n      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),\n      child: Container(\n        width: 300,\n        height: 400,\n        child: Column(\n          mainAxisSize: MainAxisSize.min,\n          crossAxisAlignment: CrossAxisAlignment.stretch,\n          children: [\n            selectedDateWidget,\n            Flexible(\n              child: Padding(\n                padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 5),\n                child: pickerWidget\n              )\n            )\n          ],\n        ),\n      )\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/components/date_time_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:intl/intl.dart';\n\nclass DateTimeInput extends StatelessWidget {\n\n  final int? initialTime;\n  final Function(DateTime) onDateChange;\n  final Function(TimeOfDay) onTimeChange;\n\n  DateTimeInput({\n    this.initialTime,\n    required this.onDateChange,\n    required this.onTimeChange,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return Row(\n      children: [\n        ElevatedButton(\n          onPressed: () {\n            showDatePicker(\n              context: context,\n              initialDate: DateTime.fromMillisecondsSinceEpoch(initialTime ?? DateTime.now().millisecondsSinceEpoch),\n              firstDate: DateTime(2015),\n              lastDate: DateTime(2025)\n            ).then((value) => {\n              if (value != null) onDateChange(value)\n            });\n          },\n          child: Text('选择日期')\n        ),\n        SizedBox(width: 5),\n        ElevatedButton(\n          onPressed: () {\n            showTimePicker(\n              context: context,\n              initialTime: TimeOfDay.now(),\n            ).then((value) => {\n              if (value != null) onTimeChange(value)\n            });\n          },\n          child: Text('选择时间')\n        ),\n        SizedBox(width: 10),\n        Expanded(\n          child: Text(DateFormat('yyyy-MM-dd kk:mm').format(DateTime.fromMillisecondsSinceEpoch(initialTime ?? DateTime.now().millisecondsSinceEpoch))),\n        )\n      ],\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/components/empty.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass Empty extends StatelessWidget {\n\n  const Empty({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return Center(\n      child: Column(\n        mainAxisAlignment: MainAxisAlignment.center,\n        crossAxisAlignment: CrossAxisAlignment.center,\n        children: [\n          const Icon(Icons.feedback_outlined, size: 70),\n          Text('无数据', style: theme.textTheme.headline4),\n        ],\n      ),\n    );\n  }\n  \n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/components/lazy_indexed_stack.dart",
    "content": "import 'package:flutter/widgets.dart';\n\nclass LazyIndexedStack extends StatefulWidget {\n  final AlignmentGeometry alignment;\n  final TextDirection? textDirection;\n  final StackFit sizing;\n  final int? index;\n\n  //reuse the created view\n  final bool reuse;\n\n  final int itemCount;\n  final IndexedWidgetBuilder itemBuilder;\n\n  LazyIndexedStack(\n      {Key? key,\n        this.alignment = AlignmentDirectional.topStart,\n        this.textDirection,\n        this.sizing = StackFit.loose,\n        this.index,\n        this.reuse = true,\n        required this.itemBuilder,\n        this.itemCount = 0})\n      : super(key: key);\n\n  @override\n  _LazyIndexedStackState createState() => _LazyIndexedStackState();\n}\n\nclass _LazyIndexedStackState extends State<LazyIndexedStack> {\n  late List<Widget> _children;\n  late List<bool> _loaded;\n\n  @override\n  void initState() {\n    _loaded = [];\n    _children = [];\n    for (int i = 0; i < widget.itemCount; ++i) {\n      if (i == widget.index) {\n        _children.add(widget.itemBuilder(context, i));\n        _loaded.add(true);\n      } else {\n        _children.add(new Container());\n        _loaded.add(false);\n      }\n    }\n    super.initState();\n  }\n\n  @override\n  void didUpdateWidget(LazyIndexedStack oldWidget) {\n    for (int i = 0; i < widget.itemCount; ++i) {\n      if (i == widget.index) {\n        if (!_loaded[i]) {\n          _children[i] = widget.itemBuilder(context, i);\n          _loaded[i] = true;\n        } else {\n          if (widget.reuse) {\n            return;\n          }\n          _children[i] = widget.itemBuilder(context, i);\n        }\n      }\n    }\n\n    super.didUpdateWidget(oldWidget);\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return new IndexedStack(\n      index: widget.index,\n      alignment: widget.alignment,\n      textDirection: widget.textDirection,\n      sizing: widget.sizing,\n      children: _children,\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/components/page_error.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass PageError extends StatelessWidget {\n\n  final String? msg;\n  final Function()? onTap;\n\n  const PageError({\n    this.msg,\n    this.onTap,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return GestureDetector(\n      onTap: onTap,\n      child: Container(\n        color: Colors.white,\n        child: Center(\n          child: Column(\n            mainAxisSize: MainAxisSize.min,\n            crossAxisAlignment: CrossAxisAlignment.center,\n            children: [\n              const Text('🙈', style: TextStyle(fontSize: 42)),\n              Text(\n                msg ?? '加载异常，点击屏幕重新加载。',\n                style: theme.textTheme.headline6,\n              ),\n            ],\n          ),\n        ),\n      ),\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/components/page_loading.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass PageLoading extends StatelessWidget {\n\n  const PageLoading({Key? key}) : super(key: key);\n\n  @override\n  Widget build(BuildContext context) {\n    return Center(\n      child: CircularProgressIndicator(),\n    );\n  }\n  \n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/components/popup_menu.dart",
    "content": "import 'package:flutter/material.dart';\n\nclass PopupMenu extends StatelessWidget {\n\n  final Function(String) onSelected;\n  final String selected;\n  final Map<String, String> items;\n\n  PopupMenu({\n    required this.onSelected,\n    required this.selected,\n    required this.items,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    final defaultStyle = Theme.of(context).textTheme.bodyText2;\n    final activeStyle = defaultStyle!.copyWith(color: Theme.of(context).colorScheme.secondary);\n    return PopupMenuButton<String>(\n      onSelected: onSelected,\n      icon: Icon(Icons.swap_vert),\n      itemBuilder: (context) => items.entries.map((i) => PopupMenuItem(\n        value: i.key,\n        child: Text(i.value, style: selected == i.key ? activeStyle : defaultStyle),\n      )).toList()\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/currency/bloc/currency_all/currency_all_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/currency/currency.dart';\n\npart 'currency_all_event.dart';\npart 'currency_all_state.dart';\n\nclass CurrencyAllBloc extends Bloc<CurrencyAllEvent, CurrencyAllState> {\n\n  final CurrencyRepository currencyRepository;\n\n  CurrencyAllBloc({\n    required this.currencyRepository\n  }) : super(CurrencyAllStateLoadInProgress()) {\n    on<CurrencyAllLoaded>(_onCurrencyAllLoaded);\n  }\n\n  void _onCurrencyAllLoaded(_, Emitter<CurrencyAllState> emit) async {\n    // 只加载一次数据\n    if (state is CurrencyAllStateLoadSuccess) return;\n      emit(CurrencyAllStateLoadInProgress());\n      final currencies = await currencyRepository.getAll();\n      emit(CurrencyAllStateLoadSuccess(currencies));\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/currency/bloc/currency_all/currency_all_event.dart",
    "content": "part of 'currency_all_bloc.dart';\n\nabstract class CurrencyAllEvent extends Equatable {\n  const CurrencyAllEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass CurrencyAllLoaded extends CurrencyAllEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/currency/bloc/currency_all/currency_all_state.dart",
    "content": "part of 'currency_all_bloc.dart';\n\nabstract class CurrencyAllState extends Equatable {\n  const CurrencyAllState();\n  @override\n  List<Object> get props => [];\n}\n\nclass CurrencyAllStateLoadInProgress extends CurrencyAllState { }\n\nclass CurrencyAllStateLoadSuccess extends CurrencyAllState {\n  final List<Currency> currencies;\n  const CurrencyAllStateLoadSuccess(this.currencies);\n  @override\n  List<Object> get props => [currencies];\n}\n\nclass CurrencyAllStateLoadFailure extends CurrencyAllState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/currency/currency.dart",
    "content": "export 'data/currency_repository.dart';\nexport 'data/models/currency.dart';\nexport 'bloc/currency_all/currency_all_bloc.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/currency/data/currency_repository.dart",
    "content": "import 'dart:convert';\nimport '/commons/commons.dart';\nimport '/currency/currency.dart';\n\nclass CurrencyRepository {\n\n  Future<List<Currency>> getAll() async {\n    String response = await HttpClient().get('currency/all');\n    return (json.decode(response)['data']).map<Currency>((i) => Currency.fromJson(i)).toList();\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/currency/data/models/currency.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'currency.g.dart';\n\n@JsonSerializable()\nclass Currency extends Equatable {\n\n  final String code;\n  final double rate;\n\n  Currency({\n    required this.code,\n    required this.rate,\n  });\n\n  factory Currency.fromJson(Map<String, dynamic> json) => _$CurrencyFromJson(json);\n\n  Map<String, dynamic> toJson() => _$CurrencyToJson(this);\n\n  @override\n  List<Object> get props => [code];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/currency/data/models/currency.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'currency.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nCurrency _$CurrencyFromJson(Map<String, dynamic> json) => Currency(\n      code: json['code'] as String,\n      rate: (json['rate'] as num).toDouble(),\n    );\n\nMap<String, dynamic> _$CurrencyToJson(Currency instance) => <String, dynamic>{\n      'code': instance.code,\n      'rate': instance.rate,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/bloc/flow_fetch/flow_fetch_bloc.dart",
    "content": "import 'dart:io';\n\nimport 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\n\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\n\npart 'flow_fetch_event.dart';\npart 'flow_fetch_state.dart';\n\nclass FlowFetchBloc extends Bloc<FlowFetchEvent, FlowFetchState> {\n\n  final FlowRepository flowRepository;\n\n  FlowFetchBloc({\n    required this.flowRepository,\n  }) : super(FlowFetchState()) {\n    on<FlowFetched>(_onFetched);\n    on<FlowImagesFetched>(_onImagesFetched);\n    on<FlowLoadDefault>(_onDefault);\n    on<FlowImageDeleted>(_onImageDeleted);\n    on<FlowImageUploaded>(_onImageUploaded);\n  }\n\n  void _onDefault(FlowLoadDefault event, Emitter<FlowFetchState> emit) {\n    emit(state.copyWith(\n      status: LoadDataStatus.success,\n      flow: event.flow\n    ));\n  }\n\n  void _onFetched(_, Emitter<FlowFetchState> emit) async {\n    try {\n      emit(state.copyWith(status: LoadDataStatus.progress));\n      final flow = await flowRepository.get(state.flow!.id);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        flow: flow,\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onImagesFetched(_, Emitter<FlowFetchState> emit) async {\n    try {\n      final images = await flowRepository.getImages(state.flow!.id);\n      emit(state.copyWith(\n        images: images,\n      ));\n    } catch (_) {\n      print(_);\n    }\n  }\n\n  void _onImageDeleted(FlowImageDeleted event, Emitter<FlowFetchState> emit) async {\n    try {\n      emit(state.copyWith(deleteImageStatus: LoadDataStatus.progress));\n      final result = await flowRepository.deleteImage(event.id);\n      if (result) {\n        emit(state.copyWith(deleteImageStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(deleteImageStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(deleteImageStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onImageUploaded(FlowImageUploaded event, Emitter<FlowFetchState> emit) async {\n    try {\n      emit(state.copyWith(uploadImageStatus: LoadDataStatus.progress));\n      final result = await flowRepository.uploadImage(event.filePath, event.userId, event.flowId);\n      if (result) {\n        emit(state.copyWith(uploadImageStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(uploadImageStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(uploadImageStatus: LoadDataStatus.failure));\n      print(_);\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/bloc/flow_fetch/flow_fetch_event.dart",
    "content": "part of 'flow_fetch_bloc.dart';\n\n@immutable\nclass FlowFetchEvent extends Equatable {\n  const FlowFetchEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass FlowFetched extends FlowFetchEvent { }\n\nclass FlowImagesFetched extends FlowFetchEvent { }\n\nclass FlowLoadDefault extends FlowFetchEvent {\n  final FlowModel flow;\n  const FlowLoadDefault({\n    required this.flow,\n  });\n  List<Object> get props => [flow];\n}\n\nclass FlowImageDeleted extends FlowFetchEvent {\n  final String id;\n  const FlowImageDeleted(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass FlowImageUploaded extends FlowFetchEvent {\n  final String filePath;\n  final String flowId;\n  final String userId;\n  const FlowImageUploaded(this.filePath, this.flowId, this.userId);\n  @override\n  List<Object> get props => [filePath, flowId, userId];\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/bloc/flow_fetch/flow_fetch_state.dart",
    "content": "part of 'flow_fetch_bloc.dart';\n\n@immutable\nclass FlowFetchState extends Equatable {\n\n  final LoadDataStatus status;\n  final FlowModel? flow;\n  final List<FlowImage> images;\n  final LoadDataStatus deleteImageStatus;\n  final LoadDataStatus uploadImageStatus;\n\n  const FlowFetchState({\n    this.status = LoadDataStatus.initial,\n    this.flow,\n    this.images = const [],\n    this.deleteImageStatus = LoadDataStatus.initial,\n    this.uploadImageStatus = LoadDataStatus.initial,\n  });\n\n  FlowFetchState copyWith({\n    LoadDataStatus? status,\n    FlowModel? flow,\n    List<FlowImage>? images,\n    LoadDataStatus? deleteImageStatus,\n    LoadDataStatus? uploadImageStatus,\n  }) {\n    return FlowFetchState(\n      status: status ?? this.status,\n      flow: flow ?? this.flow,\n      images: images ?? this.images,\n      deleteImageStatus: deleteImageStatus ?? this.deleteImageStatus,\n      uploadImageStatus: uploadImageStatus ?? this.uploadImageStatus,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, flow, images, deleteImageStatus, uploadImageStatus];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/bloc/flows/flows_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\n\npart 'flows_event.dart';\npart 'flows_state.dart';\n\nclass FlowsBloc extends Bloc<FlowsEvent, FlowsState> {\n\n  final FlowRepository flowRepository;\n\n  FlowsBloc({\n    required this.flowRepository\n  }) : super(FlowsState()) {\n    on<FlowsLoadMore>(_onLoadMore);\n    on<FlowsRefreshed>(_onRefreshed);\n    on<FlowsReset>(_onReset);\n    on<FlowsDeleted>(_onDeleted);\n    on<FlowsConfirmed>(_onConfirmed);\n    on<FlowsFilterPayeeChanged>(_onPayeeChanged);\n    on<FlowsFilterCategoryChanged>(_onCategoryChanged);\n    on<FlowsFilterTagChanged>(_onTagChanged);\n    on<FlowsFilterTypeChanged>(_onTypeChanged);\n    on<FlowsFilterStatusChanged>(_onStatusChanged);\n    on<FlowsFilterAccountIdChanged>(_onAccountChanged);\n    on<FlowsSortChanged>(_onSortChanged);\n    on<FlowsFilterMinTimeChanged>(_onMinTimeChanged);\n    on<FlowsFilterMaxTimeChanged>(_onMaxTimeChanged);\n  }\n\n  void _onRefreshed(_, Emitter<FlowsState> emit) async {\n    try {\n      var now = DateTime.now();\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n        request: state.request.copyWith(\n          minTime: state.request.minTime ?? DateTime(now.year-1, now.month, now.day).millisecondsSinceEpoch,\n          maxTime: state.request.maxTime ?? DateTime(now.year, now.month+1, now.day).millisecondsSinceEpoch,\n          page: 1\n        ),\n      ));\n      final flows = await flowRepository.query(state.request);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        flows: flows,\n        request: state.request.copyWith(page: state.request.page + 1),\n      ));\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onReset(_, Emitter<FlowsState> emit) async {\n    emit(state.copyWith(request: const FlowQueryRequest()));\n    this.add(FlowsRefreshed());\n  }\n\n  void _onLoadMore(_, Emitter<FlowsState> emit) async {\n    try {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.progress));\n      final flows = await flowRepository.query(state.request);\n      if (flows.isNotEmpty) {\n        emit(state.copyWith(\n          request: state.request.copyWith(page: state.request.page + 1),\n          flows: List.of(state.flows)..addAll(flows),\n          loadMoreStatus: LoadDataStatus.success,\n        ));\n      } else {\n        emit(state.copyWith(loadMoreStatus: LoadDataStatus.empty));\n      }\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onDeleted(FlowsDeleted event, Emitter<FlowsState> emit) async {\n    try {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.progress));\n      final result = await flowRepository.delete(event.id);\n      if (result) {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onConfirmed(FlowsConfirmed event, Emitter<FlowsState> emit) async {\n    try {\n      emit(state.copyWith(confirmStatus: LoadDataStatus.progress));\n      final result = await flowRepository.confirm(event.flow.id);\n      if (result) {\n        emit(state.copyWith(confirmStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(confirmStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(confirmStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onPayeeChanged(FlowsFilterPayeeChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(payees: [event.payeeId]),\n    ));\n  }\n\n  void _onCategoryChanged(FlowsFilterCategoryChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(categories: [event.categoryId]),\n    ));\n  }\n\n  void _onTagChanged(FlowsFilterTagChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(tags: [event.tagId]),\n    ));\n  }\n\n  void _onTypeChanged(FlowsFilterTypeChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(type: event.type),\n    ));\n  }\n\n  void _onStatusChanged(FlowsFilterStatusChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(status: event.status),\n    ));\n  }\n\n  void _onAccountChanged(FlowsFilterAccountIdChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(accountId: event.accountId),\n    ));\n  }\n\n  void _onSortChanged(FlowsSortChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(sort: event.sort),\n    ));\n  }\n\n  void _onMinTimeChanged(FlowsFilterMinTimeChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(minTime: event.minTime),\n    ));\n  }\n\n  void _onMaxTimeChanged(FlowsFilterMaxTimeChanged event, Emitter<FlowsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(maxTime: event.maxTime),\n    ));\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/bloc/flows/flows_event.dart",
    "content": "part of 'flows_bloc.dart';\n\nabstract class FlowsEvent extends Equatable {\n  const FlowsEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass FlowsLoadMore extends FlowsEvent {}\n\nclass FlowsRefreshed extends FlowsEvent {}\n\nclass FlowsReset extends FlowsEvent {}\n\nclass FlowsDeleted extends FlowsEvent {\n  final String id;\n  const FlowsDeleted(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass FlowsConfirmed extends FlowsEvent {\n  final FlowModel flow;\n  const FlowsConfirmed(this.flow);\n  @override\n  List<Object> get props => [flow];\n}\n\nclass FlowsFilterPayeeChanged extends FlowsEvent {\n  const FlowsFilterPayeeChanged(this.payeeId);\n  final String payeeId;\n  @override\n  List<Object> get props => [payeeId];\n}\n\nclass FlowsFilterCategoryChanged extends FlowsEvent {\n  const FlowsFilterCategoryChanged(this.categoryId);\n  final String categoryId;\n  @override\n  List<Object> get props => [categoryId];\n}\n\nclass FlowsFilterTagChanged extends FlowsEvent {\n  const FlowsFilterTagChanged(this.tagId);\n  final String tagId;\n  @override\n  List<Object> get props => [tagId];\n}\n\nclass FlowsFilterTypeChanged extends FlowsEvent {\n  const FlowsFilterTypeChanged(this.type);\n  final String type;\n  @override\n  List<Object> get props => [type];\n}\n\nclass FlowsFilterStatusChanged extends FlowsEvent {\n  const FlowsFilterStatusChanged(this.status);\n  final String status;\n  @override\n  List<Object> get props => [status];\n}\n\nclass FlowsFilterAccountIdChanged extends FlowsEvent {\n  const FlowsFilterAccountIdChanged(this.accountId);\n  final String accountId;\n  @override\n  List<Object> get props => [accountId];\n}\n\nclass FlowsFilterMinTimeChanged extends FlowsEvent {\n  const FlowsFilterMinTimeChanged(this.minTime);\n  final int minTime;\n  @override\n  List<Object> get props => [minTime];\n}\n\nclass FlowsFilterMaxTimeChanged extends FlowsEvent {\n  const FlowsFilterMaxTimeChanged(this.maxTime);\n  final int maxTime;\n  @override\n  List<Object> get props => [maxTime];\n}\n\nclass FlowsSortChanged extends FlowsEvent {\n  const FlowsSortChanged(this.sort);\n  final String sort;\n  @override\n  List<Object> get props => [sort];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/bloc/flows/flows_state.dart",
    "content": "part of 'flows_bloc.dart';\n\nclass FlowsState extends Equatable {\n\n  const FlowsState({\n    this.status = LoadDataStatus.initial,\n    this.flows = const <FlowModel>[],\n    this.request = const FlowQueryRequest(),\n    this.loadMoreStatus = LoadDataStatus.initial,\n    this.deleteStatus = LoadDataStatus.initial,\n    this.confirmStatus = LoadDataStatus.initial,\n  });\n\n  final LoadDataStatus status;\n  final List<FlowModel> flows;\n  final FlowQueryRequest request;\n  final LoadDataStatus loadMoreStatus;\n  final LoadDataStatus deleteStatus;\n  final LoadDataStatus confirmStatus;\n\n  FlowsState copyWith({\n    LoadDataStatus? status,\n    List<FlowModel>? flows,\n    FlowQueryRequest? request,\n    LoadDataStatus? loadMoreStatus,\n    LoadDataStatus? deleteStatus,\n    LoadDataStatus? confirmStatus\n  }) {\n    return FlowsState(\n      status: status ?? this.status,\n      flows: flows ?? this.flows,\n      request: request ?? this.request,\n      loadMoreStatus: loadMoreStatus ?? this.loadMoreStatus,\n      deleteStatus: deleteStatus ?? this.deleteStatus,\n      confirmStatus: confirmStatus ?? this.confirmStatus\n    );\n  }\n\n  @override\n  List<Object> get props => [status, flows, request, loadMoreStatus, deleteStatus, confirmStatus];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/flow_repository.dart",
    "content": "import 'dart:convert';\nimport 'package:dio/dio.dart';\n\nimport '/commons/commons.dart';\nimport '/add_flow/add_flow.dart';\nimport '/flows/flows.dart';\n\nclass FlowRepository {\n\n  Future<bool> saveExpense(DealAddRequest request) async {\n    String response = await HttpClient().post('expenses', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> refundExpense(int id, DealAddRequest request) async {\n    String response = await HttpClient().post('expenses/$id/refund', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> updateExpense(int id, DealAddRequest request) async {\n    String response = await HttpClient().put('expenses/$id', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> saveIncome(DealAddRequest request) async {\n    String response = await HttpClient().post('incomes', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> refundIncome(int id, DealAddRequest request) async {\n    String response = await HttpClient().post('incomes/$id/refund', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> updateIncome(int id, DealAddRequest request) async {\n    String response = await HttpClient().put('incomes/$id', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> saveTransfer(TransferAddRequest request) async {\n    String response = await HttpClient().post('transfers', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> updateTransfer(int id, TransferAddRequest request) async {\n    String response = await HttpClient().put('transfers/$id', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<List<FlowModel>> query(FlowQueryRequest request) async {\n    String response = await HttpClient().get('flows', params: request.toJson());\n    var responseDecoded = json.decode(response);\n    return (responseDecoded['data']['result']['content']).map<FlowModel>((i) => FlowModel.fromJson(i)).toList();\n  }\n\n  Future<FlowModel> get(int id) async {\n    String response = await HttpClient().get('flows/$id');\n    return FlowModel.fromJson(json.decode(response)['data']);\n  }\n\n  Future<bool> confirm(int id) async {\n    String response = await HttpClient().put('flows/$id/confirm');\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> delete(String id) async {\n    String response = await HttpClient().delete('flows/$id');\n    return json.decode(response)['success'];\n  }\n\n  Future<List<FlowImage>> getImages(int id) async {\n    String response = await HttpClient().get('flows/$id/images');\n    return json.decode(response)['data'].map<FlowImage>((i) => FlowImage.fromJson(i)).toList();\n  }\n\n  Future<bool> deleteImage(String id) async {\n    String response = await HttpClient().delete('flow-images/$id');\n    return json.decode(response)['success'];\n  }\n\n  Future<String> getUploadToken() async {\n    String response = await HttpClient().get('flow-images/upload-token');\n    return json.decode(response)['data'];\n  }\n\n  Future<bool> uploadImage(String filePath, String userId, String flowId) async {\n    String token = await getUploadToken();\n    MultipartFile file = await MultipartFile.fromFile(filePath);\n    String response = await HttpClient().upload({\n      'file': file,\n      'token': token,\n      'x:userId': userId\n    });\n    await HttpClient().post('flows/$flowId/image', data: json.decode(response)['data']['id']);\n    return true;\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/adjust_balance.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\nimport '/commons/commons.dart';\n\npart 'adjust_balance.g.dart';\n\n@JsonSerializable()\nclass AdjustBalance extends Equatable {\n\n  final int id;\n  final num amount;\n  final IdNameModel? account;\n  final String accountName;\n  final int createTime;\n  final String? description;\n  final String? notes;\n  final int status;\n\n  AdjustBalance({\n    required this.id,\n    required this.amount,\n    required this.account,\n    required this.accountName,\n    required this.createTime,\n    this.description,\n    this.notes,\n    required this.status\n  });\n\n  factory AdjustBalance.fromJson(Map<String, dynamic> json) => _$AdjustBalanceFromJson(json);\n\n  Map<String, dynamic> toJson() => _$AdjustBalanceToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/adjust_balance.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'adjust_balance.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nAdjustBalance _$AdjustBalanceFromJson(Map<String, dynamic> json) =>\n    AdjustBalance(\n      id: json['id'] as int,\n      amount: json['amount'] as num,\n      account: json['account'] == null\n          ? null\n          : IdNameModel.fromJson(json['account'] as Map<String, dynamic>),\n      accountName: json['accountName'] as String,\n      createTime: json['createTime'] as int,\n      description: json['description'] as String?,\n      notes: json['notes'] as String?,\n      status: json['status'] as int,\n    );\n\nMap<String, dynamic> _$AdjustBalanceToJson(AdjustBalance instance) =>\n    <String, dynamic>{\n      'id': instance.id,\n      'amount': instance.amount,\n      'account': instance.account,\n      'accountName': instance.accountName,\n      'createTime': instance.createTime,\n      'description': instance.description,\n      'notes': instance.notes,\n      'status': instance.status,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/category.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'category.g.dart';\n\n@JsonSerializable()\nclass Category extends Equatable {\n\n  final int id;\n  final num amount;\n  final num convertedAmount;\n  final int categoryId;\n  final String categoryName;\n\n  Category({\n    required this.id,\n    required this.amount,\n    required this.convertedAmount,\n    required this.categoryId,\n    required this.categoryName,\n  });\n\n  factory Category.fromJson(Map<String, dynamic> json) => _$CategoryFromJson(json);\n\n  Map<String, dynamic> toJson() => _$CategoryToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/category.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'category.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nCategory _$CategoryFromJson(Map<String, dynamic> json) => Category(\n      id: json['id'] as int,\n      amount: json['amount'] as num,\n      convertedAmount: json['convertedAmount'] as num,\n      categoryId: json['categoryId'] as int,\n      categoryName: json['categoryName'] as String,\n    );\n\nMap<String, dynamic> _$CategoryToJson(Category instance) => <String, dynamic>{\n      'id': instance.id,\n      'amount': instance.amount,\n      'convertedAmount': instance.convertedAmount,\n      'categoryId': instance.categoryId,\n      'categoryName': instance.categoryName,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/deal.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\nimport '/commons/commons.dart';\nimport 'category.dart';\nimport 'tag.dart';\n\npart 'deal.g.dart';\n\n@JsonSerializable()\nclass Deal extends Equatable {\n\n  final int id;\n  final num amount;\n  final num convertedAmount;\n  final IdNameModel? account;\n  final String? accountName;\n  final int createTime;\n  final String? description;\n  final String? notes;\n  final int status;\n  final List<Category> categories;\n  final String categoryName;\n  final List<Tag>? tags;\n  final IdNameModel? payee;\n\n  Deal({\n    required this.id,\n    required this.amount,\n    required this.convertedAmount,\n    this.account,\n    this.accountName,\n    required this.createTime,\n    this.description,\n    this.notes,\n    required this.status,\n    required this.categories,\n    required this.categoryName,\n    this.tags,\n    this.payee\n  });\n\n  factory Deal.fromJson(Map<String, dynamic> json) => _$DealFromJson(json);\n\n  Map<String, dynamic> toJson() => _$DealToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/deal.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'deal.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nDeal _$DealFromJson(Map<String, dynamic> json) => Deal(\n      id: json['id'] as int,\n      amount: json['amount'] as num,\n      convertedAmount: json['convertedAmount'] as num,\n      account: json['account'] == null\n          ? null\n          : IdNameModel.fromJson(json['account'] as Map<String, dynamic>),\n      accountName: json['accountName'] as String?,\n      createTime: json['createTime'] as int,\n      description: json['description'] as String?,\n      notes: json['notes'] as String?,\n      status: json['status'] as int,\n      categories: (json['categories'] as List<dynamic>)\n          .map((e) => Category.fromJson(e as Map<String, dynamic>))\n          .toList(),\n      categoryName: json['categoryName'] as String,\n      tags: (json['tags'] as List<dynamic>?)\n          ?.map((e) => Tag.fromJson(e as Map<String, dynamic>))\n          .toList(),\n      payee: json['payee'] == null\n          ? null\n          : IdNameModel.fromJson(json['payee'] as Map<String, dynamic>),\n    );\n\nMap<String, dynamic> _$DealToJson(Deal instance) => <String, dynamic>{\n      'id': instance.id,\n      'amount': instance.amount,\n      'convertedAmount': instance.convertedAmount,\n      'account': instance.account,\n      'accountName': instance.accountName,\n      'createTime': instance.createTime,\n      'description': instance.description,\n      'notes': instance.notes,\n      'status': instance.status,\n      'categories': instance.categories,\n      'categoryName': instance.categoryName,\n      'tags': instance.tags,\n      'payee': instance.payee,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/flow.dart",
    "content": "import 'package:bookkeeping_user_flutter/flows/data/models/adjust_balance.dart';\nimport 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\nimport 'deal.dart';\nimport 'tag.dart';\nimport 'transfer.dart';\nimport '/commons/commons.dart';\n\npart 'flow.g.dart';\n\n@JsonSerializable()\nclass FlowModel extends Equatable {\n\n  final int id;\n  final double amount;\n  final String amountFormatted;\n  final double? convertedAmount;\n  final bool needConvert;\n  final String? toCurrencyCode;\n  final String? accountName;\n  final String title;\n  final String subTitle;\n  final int createTime;\n  final String createTimeFormatted;\n  final String? description;\n  final String? notes;\n  final String? categoryName;\n  final IdNameModel? payee;\n  final int status;\n  final String statusName;\n  final List<Tag>? tags;\n  final String? tagsName;\n  final int type;\n  final String typeName;\n\n  final Deal? expense;\n  final Deal? income;\n  final Transfer? transfer;\n  final AdjustBalance? adjustBalance;\n\n  FlowModel({\n    required this.id,\n    required this.amount,\n    required this.amountFormatted,\n    this.convertedAmount,\n    required this.needConvert,\n    this.toCurrencyCode,\n    this.accountName,\n    required this.title,\n    required this.subTitle,\n    required this.createTime,\n    required this.createTimeFormatted,\n    this.description,\n    this.notes,\n    this.categoryName,\n    this.payee,\n    required this.status,\n    required this.statusName,\n    this.tags,\n    this.tagsName,\n    required this.type,\n    required this.typeName,\n    this.expense,\n    this.income,\n    this.transfer,\n    this.adjustBalance\n  });\n\n  factory FlowModel.fromJson(Map<String, dynamic> json) => _$FlowModelFromJson(json);\n\n  Map<String, dynamic> toJson() => _$FlowModelToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/flow.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'flow.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nFlowModel _$FlowModelFromJson(Map<String, dynamic> json) => FlowModel(\n      id: json['id'] as int,\n      amount: (json['amount'] as num).toDouble(),\n      amountFormatted: json['amountFormatted'] as String,\n      convertedAmount: (json['convertedAmount'] as num?)?.toDouble(),\n      needConvert: json['needConvert'] as bool,\n      toCurrencyCode: json['toCurrencyCode'] as String?,\n      accountName: json['accountName'] as String?,\n      title: json['title'] as String,\n      subTitle: json['subTitle'] as String,\n      createTime: json['createTime'] as int,\n      createTimeFormatted: json['createTimeFormatted'] as String,\n      description: json['description'] as String?,\n      notes: json['notes'] as String?,\n      categoryName: json['categoryName'] as String?,\n      payee: json['payee'] == null\n          ? null\n          : IdNameModel.fromJson(json['payee'] as Map<String, dynamic>),\n      status: json['status'] as int,\n      statusName: json['statusName'] as String,\n      tags: (json['tags'] as List<dynamic>?)\n          ?.map((e) => Tag.fromJson(e as Map<String, dynamic>))\n          .toList(),\n      tagsName: json['tagsName'] as String?,\n      type: json['type'] as int,\n      typeName: json['typeName'] as String,\n      expense: json['expense'] == null\n          ? null\n          : Deal.fromJson(json['expense'] as Map<String, dynamic>),\n      income: json['income'] == null\n          ? null\n          : Deal.fromJson(json['income'] as Map<String, dynamic>),\n      transfer: json['transfer'] == null\n          ? null\n          : Transfer.fromJson(json['transfer'] as Map<String, dynamic>),\n      adjustBalance: json['adjustBalance'] == null\n          ? null\n          : AdjustBalance.fromJson(\n              json['adjustBalance'] as Map<String, dynamic>),\n    );\n\nMap<String, dynamic> _$FlowModelToJson(FlowModel instance) => <String, dynamic>{\n      'id': instance.id,\n      'amount': instance.amount,\n      'amountFormatted': instance.amountFormatted,\n      'convertedAmount': instance.convertedAmount,\n      'needConvert': instance.needConvert,\n      'toCurrencyCode': instance.toCurrencyCode,\n      'accountName': instance.accountName,\n      'title': instance.title,\n      'subTitle': instance.subTitle,\n      'createTime': instance.createTime,\n      'createTimeFormatted': instance.createTimeFormatted,\n      'description': instance.description,\n      'notes': instance.notes,\n      'categoryName': instance.categoryName,\n      'payee': instance.payee,\n      'status': instance.status,\n      'statusName': instance.statusName,\n      'tags': instance.tags,\n      'tagsName': instance.tagsName,\n      'type': instance.type,\n      'typeName': instance.typeName,\n      'expense': instance.expense,\n      'income': instance.income,\n      'transfer': instance.transfer,\n      'adjustBalance': instance.adjustBalance,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/flow_image.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'flow_image.g.dart';\n\n@JsonSerializable()\nclass FlowImage extends Equatable {\n\n  final int id;\n  final String url;\n  final int userId;\n  final int? dealId;\n\n  FlowImage({\n    required this.id,\n    required this.url,\n    required this.userId,\n    this.dealId,\n  });\n\n  factory FlowImage.fromJson(Map<String, dynamic> json) => _$FlowImageFromJson(json);\n\n  Map<String, dynamic> toJson() => _$FlowImageToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/flow_image.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'flow_image.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nFlowImage _$FlowImageFromJson(Map<String, dynamic> json) => FlowImage(\n      id: json['id'] as int,\n      url: json['url'] as String,\n      userId: json['userId'] as int,\n      dealId: json['dealId'] as int?,\n    );\n\nMap<String, dynamic> _$FlowImageToJson(FlowImage instance) => <String, dynamic>{\n      'id': instance.id,\n      'url': instance.url,\n      'userId': instance.userId,\n      'dealId': instance.dealId,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/flow_query_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'flow_query_request.g.dart';\n\n@JsonSerializable()\nclass FlowQueryRequest extends Equatable {\n\n  final List<String>? payees;\n  final String? type;//1-支出 2-收入 3-转账 4-余额调整\n  final String? accountId;\n  final String? status;//\n  final List<String>? tags;\n  final List<String>? categories;\n  final int? minTime;\n  final int? maxTime;\n  final int page;\n  final int size;\n  final String sort;\n\n  const FlowQueryRequest({\n    this.payees,\n    this.type,\n    this.accountId,\n    this.status,\n    this.tags,\n    this.categories,\n    this.minTime,\n    this.maxTime,\n    this.page = 1,\n    this.size = 10,\n    this.sort = 'createTime,desc'\n  });\n\n  factory FlowQueryRequest.fromJson(Map<String, dynamic> json) => _$FlowQueryRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$FlowQueryRequestToJson(this);\n\n  @override\n  List<Object?> get props => [payees, type, accountId, status, tags, categories, minTime, maxTime, page, sort];\n\n  FlowQueryRequest copyWith({\n    List<String>? payees,\n    String? type,\n    String? accountId,\n    String? status,\n    List<String>? tags,\n    List<String>? categories,\n    int? minTime,\n    int? maxTime,\n    int? page,\n    int? size,\n    String? sort,\n  }) {\n    return FlowQueryRequest(\n      payees: payees ?? this.payees,\n      type: type ?? this.type,\n      accountId: accountId ?? this.accountId,\n      status: status ?? this.status,\n      tags: tags ?? this.tags,\n      categories: categories ?? this.categories,\n      minTime: minTime ?? this.minTime,\n      maxTime: maxTime ?? this.maxTime,\n      page: page ?? this.page,\n      size: size ?? this.size,\n      sort: sort ?? this.sort,\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/flow_query_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'flow_query_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nFlowQueryRequest _$FlowQueryRequestFromJson(Map<String, dynamic> json) =>\n    FlowQueryRequest(\n      payees:\n          (json['payees'] as List<dynamic>?)?.map((e) => e as String).toList(),\n      type: json['type'] as String?,\n      accountId: json['accountId'] as String?,\n      status: json['status'] as String?,\n      tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),\n      categories: (json['categories'] as List<dynamic>?)\n          ?.map((e) => e as String)\n          .toList(),\n      minTime: json['minTime'] as int?,\n      maxTime: json['maxTime'] as int?,\n      page: json['page'] as int? ?? 1,\n      size: json['size'] as int? ?? 10,\n      sort: json['sort'] as String? ?? 'createTime,desc',\n    );\n\nMap<String, dynamic> _$FlowQueryRequestToJson(FlowQueryRequest instance) =>\n    <String, dynamic>{\n      'payees': instance.payees,\n      'type': instance.type,\n      'accountId': instance.accountId,\n      'status': instance.status,\n      'tags': instance.tags,\n      'categories': instance.categories,\n      'minTime': instance.minTime,\n      'maxTime': instance.maxTime,\n      'page': instance.page,\n      'size': instance.size,\n      'sort': instance.sort,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/tag.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'tag.g.dart';\n\n@JsonSerializable()\nclass Tag extends Equatable {\n\n  final int id;\n  final double amount;\n  final double convertedAmount;\n  final int tagId;\n  final String tagName;\n\n  Tag({\n    required this.id,\n    required this.amount,\n    required this.convertedAmount,\n    required this.tagId,\n    required this.tagName,\n  });\n\n  factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);\n\n  Map<String, dynamic> toJson() => _$TagToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/tag.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'tag.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nTag _$TagFromJson(Map<String, dynamic> json) => Tag(\n      id: json['id'] as int,\n      amount: (json['amount'] as num).toDouble(),\n      convertedAmount: (json['convertedAmount'] as num).toDouble(),\n      tagId: json['tagId'] as int,\n      tagName: json['tagName'] as String,\n    );\n\nMap<String, dynamic> _$TagToJson(Tag instance) => <String, dynamic>{\n      'id': instance.id,\n      'amount': instance.amount,\n      'convertedAmount': instance.convertedAmount,\n      'tagId': instance.tagId,\n      'tagName': instance.tagName,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/transfer.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\nimport 'tag.dart';\nimport '/commons/commons.dart';\n\npart 'transfer.g.dart';\n\n@JsonSerializable()\nclass Transfer extends Equatable {\n\n  final int id;\n  final num amount;\n  final num convertedAmount;\n  final IdNameModel from;\n  final IdNameModel to;\n  final String accountName;\n  final int createTime;\n  final String? description;\n  final String? notes;\n  final int status;\n  final List<Tag>? tags;\n\n  Transfer({\n    required this.id,\n    required this.amount,\n    required this.convertedAmount,\n    required this.from,\n    required this.to,\n    required this.accountName,\n    required this.createTime,\n    this.description,\n    this.notes,\n    required this.status,\n    this.tags,\n  });\n\n  factory Transfer.fromJson(Map<String, dynamic> json) => _$TransferFromJson(json);\n\n  Map<String, dynamic> toJson() => _$TransferToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/data/models/transfer.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'transfer.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nTransfer _$TransferFromJson(Map<String, dynamic> json) => Transfer(\n      id: json['id'] as int,\n      amount: json['amount'] as num,\n      convertedAmount: json['convertedAmount'] as num,\n      from: IdNameModel.fromJson(json['from'] as Map<String, dynamic>),\n      to: IdNameModel.fromJson(json['to'] as Map<String, dynamic>),\n      accountName: json['accountName'] as String,\n      createTime: json['createTime'] as int,\n      description: json['description'] as String?,\n      notes: json['notes'] as String?,\n      status: json['status'] as int,\n      tags: (json['tags'] as List<dynamic>?)\n          ?.map((e) => Tag.fromJson(e as Map<String, dynamic>))\n          .toList(),\n    );\n\nMap<String, dynamic> _$TransferToJson(Transfer instance) => <String, dynamic>{\n      'id': instance.id,\n      'amount': instance.amount,\n      'convertedAmount': instance.convertedAmount,\n      'from': instance.from,\n      'to': instance.to,\n      'accountName': instance.accountName,\n      'createTime': instance.createTime,\n      'description': instance.description,\n      'notes': instance.notes,\n      'status': instance.status,\n      'tags': instance.tags,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/flows.dart",
    "content": "export 'bloc/flows/flows_bloc.dart';\nexport 'bloc/flow_fetch/flow_fetch_bloc.dart';\nexport 'data/flow_repository.dart';\nexport 'data/models/flow.dart';\nexport 'data/models/deal.dart';\nexport 'data/models/transfer.dart';\nexport 'data/models/adjust_balance.dart';\nexport 'data/models/flow_query_request.dart';\nexport 'data/models/flow_image.dart';\nexport 'ui/flows_page.dart';\nexport 'ui/flow_detail.dart';\nexport 'ui/flows_filter.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/flow_detail.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:image_picker/image_picker.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\nimport '/add_flow/add_flow.dart';\nimport '/login/login.dart';\n\nclass FlowDetailPage extends StatefulWidget {\n\n  final FlowModel flow;\n  FlowDetailPage({\n    required this.flow\n  });\n\n  @override\n  State<FlowDetailPage> createState() => _FlowDetailPageState();\n}\n\n\nclass _FlowDetailPageState extends State<FlowDetailPage> {\n\n  @override\n  void initState() {\n    BlocProvider.of<FlowFetchBloc>(context).add(FlowLoadDefault(flow: widget.flow));\n    BlocProvider.of<FlowFetchBloc>(context).add(FlowImagesFetched());\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<FlowsBloc, FlowsState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              // Navigator.of(context).pop();\n              // 下面这个可以解决黑屏问题，原因未知。TODO\n              if(Navigator.canPop(context)){\n                Navigator.of(context).pop();\n              }else{\n                SystemNavigator.pop();\n              }\n            }\n          }\n        ),\n        BlocListener<FlowsBloc, FlowsState>(\n          listenWhen: (previous, current) => previous.confirmStatus != current.confirmStatus,\n          listener: (context, state) {\n            if (state.confirmStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              BlocProvider.of<FlowFetchBloc>(context).add(FlowFetched());\n              // Navigator.of(context).pop();\n            }\n          }\n        ),\n        BlocListener<FlowFetchBloc, FlowFetchState>(\n          listenWhen: (previous, current) => previous.deleteImageStatus != current.deleteImageStatus,\n          listener: (context, state) {\n            if (state.deleteImageStatus == LoadDataStatus.success) {\n              Message.success('图片删除成功！');\n              BlocProvider.of<FlowFetchBloc>(context).add(FlowImagesFetched());\n            }\n          }\n        ),\n        BlocListener<FlowFetchBloc, FlowFetchState>(\n          listenWhen: (previous, current) => previous.uploadImageStatus != current.uploadImageStatus,\n          listener: (context, state) {\n            if (state.uploadImageStatus == LoadDataStatus.success) {\n              Message.success('图片上传成功！');\n              BlocProvider.of<FlowFetchBloc>(context).add(FlowImagesFetched());\n            }\n          }\n        )\n      ],\n      child: BlocBuilder<FlowFetchBloc, FlowFetchState>(\n        builder: (context, state) {\n          return Scaffold(\n            appBar: AppBar(\n              centerTitle: true,\n              title: const Text('账单详情'),\n              actions: _buildActions(context, state)\n            ),\n            body: Builder(\n              builder: (context) {\n                switch (state.status) {\n                  case LoadDataStatus.progress:\n                  case LoadDataStatus.initial:\n                    return const PageLoading();\n                  case LoadDataStatus.success:\n                    return _buildBody(context, state);\n                  default:\n                    return PageError(onTap: () { BlocProvider.of<FlowFetchBloc>(context).add(FlowFetched()); });\n                }\n              }\n            )\n          );\n        }\n    )\n    );\n  }\n\n  List<Widget> _buildActions(BuildContext context, FlowFetchState state) {\n    FlowModel? flow = state.flow;\n    return [\n      IconButton(\n        icon: Icon(Icons.edit),\n        onPressed: state.status == LoadDataStatus.success && flow != null ? () {\n          fullDialog(context, AddFlowPage(type: 2, flow: flow));\n        } : null\n      ),\n      IconButton(\n        icon: Icon(Icons.delete),\n        onPressed: state.status == LoadDataStatus.success && flow != null ? () async {\n          if (widget.flow.status == 3 ) {\n            Message.error('已退款的账单必须先删除对应的退款记录。');\n            return;\n          }\n          if (await confirm(\n            context,\n            content: Text(widget.flow.status != 2 ? '删除账单会撤回对应账户余额变动且无法恢复' : '删除之后无法恢复' + '，确定删除吗？'),\n            textOK: Text(\"确定\"),\n            textCancel: Text(\"取消\"),\n          )) {\n            BlocProvider.of<FlowsBloc>(context).add(FlowsDeleted(widget.flow.id.toString()));\n          }\n        } : null,\n      )\n    ];\n  }\n  \n  Widget _buildActionBar(BuildContext context, FlowModel flow) {\n    final state = context.watch<AuthBloc>().state;\n    return OverflowBar(\n      overflowAlignment: OverflowBarAlignment.center,\n      spacing: 20,\n      children: [\n        ElevatedButton(\n          child: const Text('复制'),\n          onPressed: flow.type != 4 ? () {\n            fullDialog(context, AddFlowPage(type: 3, flow: flow));\n          } : null\n        ),\n        ElevatedButton(\n          child: const Text('退款'),\n          // 支出和收入，而且状态正常，不是退款的情况才能退款\n          onPressed: ((flow.type == 1 || flow.type == 2) && (flow.status == 1 && flow.amount > 0)) ? () {\n            fullDialog(context, AddFlowPage(type: 4, flow: flow));\n          } : null\n        ),\n        ElevatedButton(\n            child: const Text('确认'),\n            onPressed: flow.status == 2 ? () async {\n              if (await confirm(\n                context,\n                content: Text(\"状态将更改为确认，确定此操作吗？\"),\n                textOK: Text(\"确定\"),\n                textCancel: Text(\"取消\"),\n              )) {\n                BlocProvider.of<FlowsBloc>(context).add(FlowsConfirmed(flow));\n              }\n            } : null\n        ),\n        ElevatedButton(\n          child: const Text('图片'),\n          onPressed: () {\n            showModalBottomSheet(\n              context: context,\n              builder: (context) {\n                return Column(\n                  mainAxisSize: MainAxisSize.min,\n                  children: [\n                    ListTile(\n                      leading: new Icon(Icons.camera_alt),\n                      title: const Text('拍照'),\n                      onTap: () async {\n                        final ImagePicker _picker = ImagePicker();\n                        final XFile? photo = await _picker.pickImage(source: ImageSource.camera);\n                        if (photo != null) {\n                          BlocProvider.of<FlowFetchBloc>(context).add(FlowImageUploaded(photo.path, flow.id.toString(), state.session?.userSessionVO.id.toString() ?? ''));\n                        }\n                        Navigator.pop(context);\n                      },\n                    ),\n                    ListTile(\n                      leading: new Icon(Icons.photo_library),\n                      title: const Text('图片库'),\n                      onTap: () async {\n                        final ImagePicker _picker = ImagePicker();\n                        final XFile? photo = await _picker.pickImage(source: ImageSource.gallery);\n                        if (photo != null) {\n                          BlocProvider.of<FlowFetchBloc>(context).add(FlowImageUploaded(photo.path, flow.id.toString(), state.session?.userSessionVO.id.toString() ?? ''));\n                        }\n                        Navigator.pop(context);\n                      },\n                    ),\n                    SizedBox(height: 10),\n                    ListTile(\n                      leading: new Icon(Icons.cancel),\n                      title: const Text('取消'),\n                      onTap: () {\n                        Navigator.pop(context);\n                      },\n                    ),\n                  ],\n                );\n              }\n            );\n          }\n        ),\n      ],\n    );\n  }\n  \n  Widget _buildBody(BuildContext context, FlowFetchState state) {\n    TextStyle? style1 = Theme.of(context).textTheme.bodyText2;\n    TextStyle? style2 = Theme.of(context).textTheme.bodyText1;\n    if (state.uploadImageStatus == LoadDataStatus.progress) {\n      return const PageLoading();\n    }\n    FlowModel flow = state.flow!;\n    List<FlowImage> images = state.images;\n    return SingleChildScrollView(\n      child: Column(\n        children: [\n          // 放不下变两行可以用Wrap https://github.com/flutter/flutter/issues/53642\n          if (flow.type != 4) _buildActionBar(context, flow),\n          SizedBox(height: 15),\n          Padding(\n              padding: EdgeInsets.symmetric(horizontal: 15),\n              child: Column(\n                children: [\n                  Row(children: [Text(\"描述：\", style: style1), Text(flow.description ?? \"\", style: style2)]),\n                  SizedBox(height: 15),\n                  Row(children: [Text(\"交易类型：\", style: style1), Text(flow.typeName, style: style2)]),\n                  SizedBox(height: 15),\n                  Row(children: [Text(\"时间：\", style: style1), Text(flow.createTimeFormatted, style: style2)]),\n                  SizedBox(height: 15),\n                  Row(children: [Text(\"金额：\", style: style1), Text(flow.amount.toStringAsFixed(2), style: style2)]),\n                  SizedBox(height: 15),\n                  if (flow.needConvert)\n                    Row(children: [Text(\"折合${flow.toCurrencyCode}：\", style: style1), Text(flow.convertedAmount != null ? flow.convertedAmount.toString() : '', style: style2)]),\n                  if (flow.needConvert) SizedBox(height: 15),\n                  Row(children: [Text(\"账户：\", style: style1), Text(flow.accountName ?? \"\", style: style2)]),\n                  SizedBox(height: 15),\n                  if(flow.type == 1 || flow.type == 2) Row(children: [Text(\"类别：\", style: style1), Text(flow.categoryName ?? \"\", style: style2)]),\n                  if(flow.type == 1 || flow.type == 2) SizedBox(height: 15),\n                  if(flow.type != 4) Row(children: [Text(\"标签：\", style: style1), Text(flow.tagsName ?? \"\", style: style2)]),\n                  if(flow.type != 4) SizedBox(height: 15),\n                  if(flow.type == 1 || flow.type == 2) Row(children: [Text(\"交易对象：\", style: style1), Text(flow.payee?.name ?? \"\", style: style2)]),\n                  if(flow.type == 1 || flow.type == 2) SizedBox(height: 15),\n                  Row(children: [Text(\"状态：\", style: style1), Text(flow.statusName, style: style2)]),\n                  SizedBox(height: 15),\n                  Row(children: [Text(\"备注：\", style: style1), Flexible(child: Text(flow.notes ?? \"\", style: style2))]),\n                ],\n              )\n          ),\n          Column(\n            children: images.map((e) => (\n              Column(\n                children: [\n                  SizedBox(height: 15),\n                  GestureDetector(\n                    onTap: () async {\n                      if (await confirm(\n                        context,\n                        content: const Text('确定删除此账单图片吗？'),\n                        textOK: Text(\"确定\"),\n                        textCancel: Text(\"取消\"),\n                      )) {\n                        BlocProvider.of<FlowFetchBloc>(context).add(FlowImageDeleted(e.id.toString()));\n                      }\n                    },\n                    child: Image.network(e.url),\n                  )\n                ],\n              )\n            )).toList(),\n          ),\n        ],\n      ),\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/flows_filter.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/flows/flows.dart';\nimport 'widgets/widgets.dart';\n\nclass FlowsFilterPage extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: const Text('搜索流水'),\n        actions: [\n          IconButton(\n            icon: Icon(Icons.done),\n            onPressed: () {\n              BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n              Navigator.pop(context);\n            },\n          ),\n        ]\n      ),\n      body: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 0),\n          child: Column(\n            children: [\n              DateInput(),\n              PayeeInput(),\n              TagInput(),\n              ExpenseCategoryInput(),\n              IncomeCategoryInput(),\n              TypeInput(),\n              AccountInput(),\n              StatusInput(),\n            ],\n          )\n        )\n      )\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/flows_page.dart",
    "content": "import 'package:bookkeeping_user_flutter/routes.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:pull_to_refresh/pull_to_refresh.dart';\n\nimport '/commons/commons.dart';\nimport '/components/components.dart';\nimport '/flows/flows.dart';\nimport '/add_flow/add_flow.dart';\nimport 'widgets/widgets.dart';\n\nclass FlowsPage extends StatefulWidget {\n  @override\n  State<FlowsPage> createState() => _FlowsPageState();\n}\n\nclass _FlowsPageState extends State<FlowsPage> {\n\n  RefreshController _refreshController = RefreshController(initialRefresh: false);\n\n  @override\n  void initState() {\n    BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<FlowsBloc, FlowsState>(\n          listenWhen: (previous, current) => previous.loadMoreStatus != current.loadMoreStatus,\n          listener: (context, state) {\n            if (state.loadMoreStatus == LoadDataStatus.success) {\n              _refreshController.loadComplete();\n            } else if (state.loadMoreStatus == LoadDataStatus.failure) {\n              _refreshController.loadFailed();\n            } else if (state.loadMoreStatus == LoadDataStatus.empty) {\n              _refreshController.loadNoData();\n            }\n          }\n        ),\n        BlocListener<FlowsBloc, FlowsState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              // 不加延迟刷新会黑屏，原因未知。 TODO\n              // await Future.delayed(const Duration(milliseconds: 500));\n              BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n            }\n          }\n        ),\n        BlocListener<FlowsBloc, FlowsState>(\n          listenWhen: (previous, current) => previous.confirmStatus != current.confirmStatus,\n          listener: (context, state) {\n            if (state.confirmStatus == LoadDataStatus.success) {\n              BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n            }\n          }\n        )\n      ],\n      child: Scaffold(\n        appBar: AppBar(\n          leading: IconButton(\n              onPressed: () {\n                BlocProvider.of<FlowsBloc>(context).add(FlowsReset());\n              },\n              icon: const Icon(Icons.refresh)\n          ),\n          title: const Text(\"流水\"),\n          centerTitle: true,\n          actions: [\n            OrderButton(),\n            IconButton(\n              onPressed: () {\n                Navigator.pushNamed(context, '/flows-filter');\n              },\n              icon: const Icon(Icons.search)\n            )\n          ],\n        ),\n        body: BlocBuilder<FlowsBloc, FlowsState>(\n          buildWhen: (previous, current) => previous.status != current.status || previous.flows != current.flows,\n          builder: (context, state) {\n            switch (state.status) {\n              case LoadDataStatus.progress:\n              case LoadDataStatus.initial:\n                return const PageLoading();\n              case LoadDataStatus.success:\n                if (state.flows.isEmpty) return Empty();\n                return SmartRefresher(\n                  enablePullDown: true,\n                  enablePullUp: true,\n                  controller: _refreshController,\n                  child: _buildList(context, state.flows),\n                  onRefresh: () async {\n                    BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed());\n                    _refreshController.refreshCompleted();\n                  },\n                  onLoading: () async {\n                    BlocProvider.of<FlowsBloc>(context).add(FlowsLoadMore());\n                  },\n                );\n              default:\n                return PageError(onTap: () { BlocProvider.of<FlowsBloc>(context).add(FlowsRefreshed()); });\n            }\n          }\n        )\n      )\n    );\n  }\n\n  Widget _buildList(BuildContext context, List<FlowModel> flows) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      itemCount: flows.length,\n      itemBuilder: (context, index) {\n        FlowModel flow = flows[index];\n        TextStyle amountStyle = theme.textTheme.headline6 ?? TextStyle(fontWeight: FontWeight.w500, fontSize: 20);\n        if (flow.type == 1) amountStyle = amountStyle.copyWith(color: Colors.green);\n        if (flow.type == 2) amountStyle = amountStyle.copyWith(color: Colors.red);\n        return ListTile(\n          dense: true,\n          title: Text(flow.title, style: theme.textTheme.bodyText1),\n          subtitle: Text(flow.subTitle, style: theme.textTheme.caption),\n          trailing: Row(\n            mainAxisSize: MainAxisSize.min,\n            children: [\n              Text(flow.amountFormatted, style: amountStyle),\n              Icon(Icons.keyboard_arrow_right)\n            ],\n          ),\n          onTap: () {\n            Navigator.pushNamed(context, '/flow-detail', arguments: FlowDetailArguments(flow: flow));\n          },\n          onLongPress: flow.type != 4 ? () {\n            fullDialog(context, AddFlowPage(type: 3, flow: flow));\n          } : null,\n        );\n      },\n      separatorBuilder: (context, index) {\n        return Divider();\n      },\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/account_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\nimport '/accounts/accounts.dart';\n\nclass AccountInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<AccountEnableBloc>().state;\n    return BlocSelector<FlowsBloc, FlowsState, String?>(\n      selector: (state) => state.request.accountId,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single(\n            title: '交易账户',\n            selectedValue: state ?? '',\n            onChange: (selected) {\n              context.read<FlowsBloc>().add(FlowsFilterAccountIdChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is AccountEnableStateLoadSuccess ? modelToChoice(state1.accounts) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is AccountExpenseableStateLoadInProgress,\n              );\n            }\n          );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/date_input.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:syncfusion_flutter_datepicker/datepicker.dart';\nimport 'package:intl/intl.dart';\nimport '/components/components.dart';\nimport '/flows/flows.dart';\n\nclass DateInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<FlowsBloc, FlowsState>(\n      buildWhen: (previous, current) => previous.request.minTime != current.request.minTime || previous.request.maxTime != current.request.maxTime,\n      builder: (context, state) {\n        String dateText = '';\n        DateTime? startDate;\n        DateTime? endDate;\n        if (state.request.minTime != null) {\n          dateText = DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.minTime!));\n          dateText = dateText + ' - ';\n          startDate = DateTime.fromMillisecondsSinceEpoch(state.request.minTime!);\n        }\n        if (state.request.maxTime != null) {\n          if (state.request.minTime == null) {\n            dateText = dateText + ' - ';\n          } else {\n            dateText = dateText + DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.request.maxTime!));\n          }\n          endDate = DateTime.fromMillisecondsSinceEpoch(state.request.maxTime!);\n        }\n        return Row(\n          children: [\n            SizedBox(width: 15),\n            ElevatedButton(\n                onPressed: () async {\n                  final PickerDateRange? range =\n                  await showDialog(\n                    context: context,\n                    builder: (BuildContext context) {\n                      return DateRangePicker(PickerDateRange(startDate, endDate));\n                    }\n                  );\n                  if (range != null) {\n                    BlocProvider.of<FlowsBloc>(context).add(FlowsFilterMinTimeChanged(range.startDate!.millisecondsSinceEpoch));\n                    BlocProvider.of<FlowsBloc>(context).add(FlowsFilterMaxTimeChanged(range.endDate!.millisecondsSinceEpoch));\n                  }\n                },\n                child: Text('选择日期范围')\n            ),\n            SizedBox(width: 10),\n            Text(dateText),\n            SizedBox(width: 15),\n          ],\n        );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/expense_category_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\nimport '/categories/categories.dart';\n\nclass ExpenseCategoryInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<ExpenseCategorySelectBloc>().state;\n    return BlocSelector<FlowsBloc, FlowsState, List<String>?>(\n      selector: (state) => state.request.categories,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n            (\n              title: '支出分类',\n              selectedValue: state?.first ?? '',\n              onChange: (selected) {\n                context.read<FlowsBloc>().add(FlowsFilterCategoryChanged(selected.value ?? ''));\n              },\n              choiceItems: state1 is ExpenseCategorySelectStateLoadSuccess ? modelToChoice(state1.categories) : [],\n              choiceType: S2ChoiceType.chips,\n              modalFilter: true,\n              modalFilterAuto:true,\n              tileBuilder: (context, state) {\n                return S2Tile.fromState(\n                  state,\n                  isLoading: state1 is ExpenseCategorySelectStateLoadInProgress,\n                );\n              }\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/income_category_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\nimport '/categories/categories.dart';\n\nclass IncomeCategoryInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<IncomeCategorySelectBloc>().state;\n    return BlocSelector<FlowsBloc, FlowsState, List<String>?>(\n      selector: (state) => state.request.categories,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n            (\n              title: '收入分类',\n              selectedValue: state?.first ?? '',\n              onChange: (selected) {\n                context.read<FlowsBloc>().add(FlowsFilterCategoryChanged(selected.value ?? ''));\n              },\n              choiceItems: state1 is IncomeCategorySelectStateLoadSuccess ? modelToChoice(state1.categories) : [],\n              choiceType: S2ChoiceType.chips,\n              modalFilter: true,\n              modalFilterAuto:true,\n              tileBuilder: (context, state) {\n                return S2Tile.fromState(\n                  state,\n                  isLoading: state1 is IncomeCategorySelectStateLoadInProgress,\n                );\n              }\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/order_button.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/flows/flows.dart';\n\nclass OrderButton extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocSelector<FlowsBloc, FlowsState, String>(\n      selector: (state) => state.request.sort,\n      builder: (context, state) {\n        return PopupMenu(\n          onSelected: (selected) {\n            if (selected != state) {\n              context.read<FlowsBloc>().add(FlowsSortChanged(selected));\n              context.read<FlowsBloc>().add(FlowsRefreshed());\n            }\n          },\n          items: {\n            'createTime,desc': '按时间排序',\n            'amount,desc': '按金额排序',\n          },\n          selected: state,\n        );\n      }\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/payee_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\nimport '/payees/payees.dart';\n\nclass PayeeInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<PayeeEnableBloc>().state;\n    return BlocSelector<FlowsBloc, FlowsState, List<String>?>(\n      selector: (state) => state.request.payees,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n            (\n              title: '交易对象',\n              selectedValue: state?.first ?? '',\n              onChange: (selected) {\n                context.read<FlowsBloc>().add(FlowsFilterPayeeChanged(selected.value ?? ''));\n              },\n              choiceItems: state1 is PayeeEnableStateLoadSuccess ? modelToChoice(state1.payees) : [],\n              choiceType: S2ChoiceType.chips,\n              modalFilter: true,\n              modalFilterAuto:true,\n              tileBuilder: (context, state) {\n                return S2Tile.fromState(\n                  state,\n                  isLoading: state1 is PayeeEnableStateLoadInProgress,\n                );\n              }\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/status_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/flows/flows.dart';\n\nclass StatusInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    List<S2Choice<String>> options = [\n      S2Choice<String>(value: '1', title: '正常'),\n      S2Choice<String>(value: '2', title: '待确认'),\n      S2Choice<String>(value: '3', title: '已退款'),\n    ];\n    return BlocSelector<FlowsBloc, FlowsState, String?>(\n      selector: (state) => state.request.status,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n            (\n              title: '交易状态',\n              selectedValue: state ?? '',\n              onChange: (selected) {\n                context.read<FlowsBloc>().add(FlowsFilterStatusChanged(selected.value ?? ''));\n              },\n              choiceItems: options,\n              choiceType: S2ChoiceType.radios,\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/tag_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/commons/commons.dart';\nimport '/flows/flows.dart';\nimport '/tags/tags.dart';\n\nclass TagInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<TagEnableBloc>().state;\n    return BlocSelector<FlowsBloc, FlowsState, List<String>?>(\n      selector: (state) => state.request.tags,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n            (\n              title: '交易标签',\n              selectedValue: state?.first ?? '',\n              onChange: (selected) {\n                context.read<FlowsBloc>().add(FlowsFilterTagChanged(selected.value ?? ''));\n              },\n              choiceItems: state1 is TagEnableStateLoadSuccess ? modelToChoice(state1.tags) : [],\n              choiceType: S2ChoiceType.chips,\n              modalFilter: true,\n              modalFilterAuto:true,\n              tileBuilder: (context, state) {\n                return S2Tile.fromState(\n                  state,\n                  isLoading: state1 is TagEnableStateLoadInProgress,\n                );\n              }\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/type_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\n\nimport '/flows/flows.dart';\n\nclass TypeInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    List<S2Choice<String>> options = [\n      S2Choice<String>(value: '1', title: '支出'),\n      S2Choice<String>(value: '2', title: '收入'),\n      S2Choice<String>(value: '3', title: '转账'),\n      S2Choice<String>(value: '4', title: '余额调整'),\n    ];\n    return BlocSelector<FlowsBloc, FlowsState, String?>(\n      selector: (state) => state.request.type,\n      builder: (context, state) {\n        return\n          SmartSelect<String>.single\n            (\n              title: '交易类型',\n              selectedValue: state ?? '',\n              onChange: (selected) {\n                context.read<FlowsBloc>().add(FlowsFilterTypeChanged(selected.value ?? ''));\n              },\n              choiceItems: options,\n              choiceType: S2ChoiceType.radios,\n          );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/flows/ui/widgets/widgets.dart",
    "content": "export 'payee_input.dart';\nexport 'tag_input.dart';\nexport 'expense_category_input.dart';\nexport 'income_category_input.dart';\nexport 'type_input.dart';\nexport 'account_input.dart';\nexport 'status_input.dart';\nexport 'order_button.dart';\nexport 'date_input.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/groups/data/models/group.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'group.g.dart';\n\n@JsonSerializable()\nclass Group extends Equatable {\n\n  final int id;\n  final String name;\n  final String? notes;\n  final String defaultCurrencyCode;\n\n  const Group({\n    required this.id,\n    required this.name,\n    this.notes,\n    required this.defaultCurrencyCode,\n  });\n\n  factory Group.fromJson(Map<String, dynamic> json) => _$GroupFromJson(json);\n\n  Map<String, dynamic> toJson() => _$GroupToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/groups/data/models/group.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'group.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nGroup _$GroupFromJson(Map<String, dynamic> json) => Group(\n      id: json['id'] as int,\n      name: json['name'] as String,\n      notes: json['notes'] as String?,\n      defaultCurrencyCode: json['defaultCurrencyCode'] as String,\n    );\n\nMap<String, dynamic> _$GroupToJson(Group instance) => <String, dynamic>{\n      'id': instance.id,\n      'name': instance.name,\n      'notes': instance.notes,\n      'defaultCurrencyCode': instance.defaultCurrencyCode,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/groups/groups.dart",
    "content": "export 'data/models/group.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/index.dart",
    "content": "import 'package:flutter/material.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/accounts/accounts.dart';\nimport '/add_flow/add_flow.dart';\nimport '/flows/flows.dart';\nimport '/charts/charts.dart';\nimport '/my/my.dart';\n\nclass IndexPage extends StatefulWidget {\n\n  final int initialIndex;\n\n  const IndexPage({\n    this.initialIndex = 1,\n  });\n\n  @override\n  State<IndexPage> createState() => new _IndexPageState();\n\n}\n\nclass _IndexPageState extends State<IndexPage> {\n\n  late int _selectedIndex;\n\n  @override\n  void initState() {\n    this._selectedIndex = widget.initialIndex;\n    super.initState();\n  }\n\n  Widget buildBottomItem(int index, IconData iconData, String label) {\n    final theme = Theme.of(context);\n    Color color = _selectedIndex == index ? theme.primaryColor : theme.unselectedWidgetColor;\n    return GestureDetector(\n        onTap: () {\n          setState(() {\n            _selectedIndex = index;\n          });\n        },\n        child: Container(\n          width: 66,\n          child: Column(\n            mainAxisAlignment: MainAxisAlignment.center,\n            children: [\n              Icon(iconData, color: color),\n              Text(label, style: TextStyle(color: color))\n            ],\n          ),\n        )\n    );\n  }\n\n  static final List<Widget> _pages = <Widget>[AccountsPage(), FlowsPage(), ChartsPage(), MyPage()];\n\n  @override\n  Widget build(BuildContext context) {\n    final theme = Theme.of(context);\n    return Scaffold(\n      body: LazyIndexedStack(\n        reuse: false,\n        index: _selectedIndex,\n        itemBuilder: (c, i) {\n          return _pages[i];\n        },\n        itemCount: 4,\n      ),\n      bottomNavigationBar: Container(\n        height: 56,\n        margin: EdgeInsets.symmetric(vertical: 0),\n        decoration: BoxDecoration(\n          border: Border(\n            top: BorderSide(width: 1, color: theme.unselectedWidgetColor),\n          ),\n        ),\n        child: Row(children: [\n          buildBottomItem(0, Icons.account_balance_outlined, '账户'),\n          buildBottomItem(1, Icons.table_rows_outlined, '流水'),\n          Expanded(\n            child: GestureDetector(\n              onTap: () {\n                fullDialog(context, AddFlowPage(type: 1));\n              },\n              child: Container(\n                height: 44,\n                alignment: Alignment.center,\n                decoration: BoxDecoration(\n                  color: theme.primaryColor,\n                  borderRadius: BorderRadius.circular(50),\n                ),\n                child: Text('记账', style: theme.textTheme.headline6!.copyWith(color: theme.colorScheme.onPrimary)),\n              ),\n            ),\n          ),\n          buildBottomItem(2, Icons.pie_chart_outline, '图表'),\n          buildBottomItem(3, Icons.person_outline_outlined, '我的'),\n        ]))\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/bloc/item_form/item_form_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport 'models/models.dart';\nimport '/items/items.dart';\n\npart 'item_form_event.dart';\npart 'item_form_state.dart';\n\nclass ItemFormBloc extends Bloc<ItemFormEvent, ItemFormState> {\n\n  final ItemRepository itemRepository;\n\n  ItemFormBloc({\n    required this.itemRepository\n  }) : super(const ItemFormState()) {\n    on<ItemFormTitleChanged>(_onTitleChanged);\n    on<ItemFormNotesChanged>(_onNotesChanged);\n    on<ItemFormDefaultLoaded>(_onDefaultLoaded);\n    on<ItemFormAddSubmitted>(_onAddSubmitted);\n  }\n\n  void _onTitleChanged(ItemFormTitleChanged event, Emitter<ItemFormState> emit) {\n    final title = Title.dirty(event.title);\n    emit(state.copyWith(\n      title: title,\n      status: Formz.validate([title]),\n    ));\n  }\n\n  void _onNotesChanged(ItemFormNotesChanged event, Emitter<ItemFormState> emit) {\n    emit(state.copyWith(\n      notes: event.notes,\n    ));\n  }\n\n  void _onDefaultLoaded(ItemFormDefaultLoaded event, Emitter<ItemFormState> emit) {\n    var now = DateTime.now();\n    emit(state.copyWith(\n      status: FormzStatus.pure,\n      startDate: now.millisecondsSinceEpoch,\n      endDate: DateTime(now.year+100, now.month, now.day).millisecondsSinceEpoch\n    ));\n  }\n\n  void _onAddSubmitted(ItemFormAddSubmitted event, Emitter<ItemFormState> emit) async {\n    if (state.status.isValidated) {\n      emit(state.copyWith(status: FormzStatus.submissionInProgress));\n      try {\n        await itemRepository.save(\n          ItemAddRequest(\n            title: state.title.value,\n            notes: state.notes,\n            startDate: 1232,\n            endDate: 124142,\n            repeatType: 1,\n            interval: 2\n          )\n        );\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n      } catch (_) {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/bloc/item_form/item_form_event.dart",
    "content": "part of 'item_form_bloc.dart';\n\nabstract class ItemFormEvent extends Equatable {\n  const ItemFormEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass ItemFormTitleChanged extends ItemFormEvent {\n  const ItemFormTitleChanged(this.title);\n  final String title;\n  @override\n  List<Object> get props => [title];\n}\n\nclass ItemFormNotesChanged extends ItemFormEvent {\n  const ItemFormNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass ItemFormStartDateChanged extends ItemFormEvent {\n  const ItemFormStartDateChanged(this.startDate);\n  final int startDate;\n  @override\n  List<Object> get props => [startDate];\n}\n\nclass ItemFormEndDateChanged extends ItemFormEvent {\n  const ItemFormEndDateChanged(this.endDate);\n  final int endDate;\n  @override\n  List<Object> get props => [endDate];\n}\n\nclass ItemFormRepeatTypeChanged extends ItemFormEvent {\n  const ItemFormRepeatTypeChanged(this.repeatType);\n  final int repeatType;\n  @override\n  List<Object> get props => [repeatType];\n}\n\nclass ItemFormIntervalChanged extends ItemFormEvent {\n  const ItemFormIntervalChanged(this.interval);\n  final int interval;\n  @override\n  List<Object> get props => [interval];\n}\n\nclass ItemFormDefaultLoaded extends ItemFormEvent {\n  final int type;\n  final Item? item;\n  const ItemFormDefaultLoaded(this.type, this.item);\n  @override\n  List<Object?> get props => [type, item];\n}\n\nclass ItemFormAddSubmitted extends ItemFormEvent {\n  const ItemFormAddSubmitted();\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/bloc/item_form/item_form_state.dart",
    "content": "part of 'item_form_bloc.dart';\n\nclass ItemFormState extends Equatable {\n\n  final FormzStatus status;\n  final Title title;\n  final String? notes;\n  final int? startDate;\n  final int? endDate;\n  final int? repeatType;\n  final int? interval;\n\n  const ItemFormState({\n    this.status = FormzStatus.pure,\n    this.title = const Title.pure(),\n    this.notes,\n    this.startDate = 1,\n    this.endDate = 2,\n    this.repeatType,\n    this.interval\n  });\n\n  @override\n  List<Object?> get props => [status, title, notes, startDate, endDate, repeatType, interval];\n\n  ItemFormState copyWith({\n    FormzStatus? status,\n    Title? title,\n    String? notes,\n    int? startDate,\n    int? endDate,\n    int? repeatType,\n    int? interval,\n  }) {\n    return ItemFormState(\n      status: status ?? this.status,\n      title: title ?? this.title,\n      notes: notes ?? this.notes,\n      startDate: startDate ?? this.startDate,\n      endDate: endDate ?? this.endDate,\n      repeatType: repeatType ?? this.repeatType,\n      interval: interval ?? this.interval,\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/bloc/item_form/models/models.dart",
    "content": "export 'title.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/bloc/item_form/models/title.dart",
    "content": "import 'package:formz/formz.dart';\n\nenum TitleValidationError { empty }\n\nclass Title extends FormzInput<String, TitleValidationError> {\n  const Title.pure() : super.pure('');\n  const Title.dirty([String value = '']) : super.dirty(value);\n\n  @override\n  TitleValidationError? validator(String? value) {\n    return value?.isNotEmpty == true ? null : TitleValidationError.empty;\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/bloc/items/items_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\nimport '/commons/commons.dart';\nimport '/items/items.dart';\n\npart 'items_event.dart';\npart 'items_state.dart';\n\nclass ItemsBloc extends Bloc<ItemsEvent, ItemsState> {\n\n  final ItemRepository itemRepository;\n\n  ItemsBloc({\n    required this.itemRepository,\n  }) : super(ItemsState()) {\n    on<ItemsRefreshed>(_onRefreshed);\n    on<ItemsLoadMore>(_onLoadMore);\n    on<ItemsSortChanged>(_onSortChanged);\n    on<ItemDeleted>(_onDeleted);\n  }\n\n  void _onRefreshed(_, Emitter<ItemsState> emit) async {\n    try {\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n        request: state.request.copyWith(page: 1),\n      ));\n      final items = await itemRepository.query(state.request);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        items: items,\n        request: state.request.copyWith(page: state.request.page + 1),\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onLoadMore(_, Emitter<ItemsState> emit) async {\n    try {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.progress));\n      final items = await itemRepository.query(state.request);\n      if (items.isNotEmpty) {\n        emit(state.copyWith(\n          request: state.request.copyWith(page: state.request.page + 1),\n          items: List.of(state.items)..addAll(items),\n          loadMoreStatus: LoadDataStatus.success,\n        ));\n      } else {\n        emit(state.copyWith(loadMoreStatus: LoadDataStatus.empty));\n      }\n    } catch (_) {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onSortChanged(ItemsSortChanged event, Emitter<ItemsState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(sort: event.sort),\n    ));\n  }\n\n  void _onDeleted(ItemDeleted event, Emitter<ItemsState> emit) async {\n    try {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.progress));\n      final result = await itemRepository.delete(event.id);\n      if (result) {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/bloc/items/items_event.dart",
    "content": "part of 'items_bloc.dart';\n\n@immutable\nabstract class ItemsEvent extends Equatable {\n  const ItemsEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass ItemsRefreshed extends ItemsEvent {}\n\nclass ItemsLoadMore extends ItemsEvent {}\n\nclass ItemDeleted extends ItemsEvent {\n  final String id;\n  const ItemDeleted(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass ItemsSortChanged extends ItemsEvent {\n  const ItemsSortChanged(this.sort);\n  final String sort;\n  @override\n  List<Object> get props => [sort];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/bloc/items/items_state.dart",
    "content": "part of 'items_bloc.dart';\n\n@immutable\nclass ItemsState extends Equatable {\n\n  final LoadDataStatus status;\n  final List<Item> items;\n  final ItemQueryRequest request;\n  final LoadDataStatus loadMoreStatus;\n  final LoadDataStatus deleteStatus;\n\n  const ItemsState({\n    this.status = LoadDataStatus.initial,\n    this.items = const <Item>[],\n    this.request = const ItemQueryRequest(),\n    this.loadMoreStatus = LoadDataStatus.initial,\n    this.deleteStatus = LoadDataStatus.initial,\n  });\n\n  ItemsState copyWith({\n    LoadDataStatus? status,\n    List<Item>? items,\n    ItemQueryRequest? request,\n    LoadDataStatus? loadMoreStatus,\n    LoadDataStatus? deleteStatus,\n  }) {\n    return ItemsState(\n      status: status ?? this.status,\n      items: items ?? this.items,\n      request: request ?? this.request,\n      loadMoreStatus: loadMoreStatus ?? this.loadMoreStatus,\n      deleteStatus: deleteStatus ?? this.deleteStatus,\n    );\n  }\n\n  @override\n  List<Object> get props => [status, request, loadMoreStatus, deleteStatus];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/data/item_repository.dart",
    "content": "import 'dart:convert';\n\nimport '/commons/commons.dart';\nimport '/items/items.dart';\n\nclass ItemRepository {\n\n  Future<bool> save(ItemAddRequest request) async {\n    String response = await HttpClient().post('items', data: request.toJson());\n    return json.decode(response)['success'];\n  }\n\n  Future<bool> update(int id, ItemAddRequest request) async {\n    String response = await HttpClient().put('items/$id', data: request.toJson());\n    return parseResponse(response);\n  }\n\n  Future<List<Item>> query(ItemQueryRequest request) async {\n    String response = await HttpClient().get('items', params: request.toJson());\n    return json.decode(response)['data']['content'].map<Item>((i) => Item.fromJson(i)).toList();\n  }\n\n  Future<bool> delete(String id) async {\n    String response = await HttpClient().delete('items/$id');\n    return parseResponse(response);\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/data/models/item.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'item.g.dart';\n\n@JsonSerializable()\nclass Item extends Equatable {\n\n  final int id;\n  final String title;\n  final String? notes;\n  final int nextDate;\n  final int countDown;\n\n  Item({\n    required this.id,\n    required this.title,\n    this.notes,\n    required this.nextDate,\n    required this.countDown,\n  });\n\n  factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);\n\n  Map<String, dynamic> toJson() => _$ItemToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/data/models/item.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'item.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nItem _$ItemFromJson(Map<String, dynamic> json) => Item(\n      id: json['id'] as int,\n      title: json['title'] as String,\n      notes: json['notes'] as String?,\n      nextDate: json['nextDate'] as int,\n      countDown: json['countDown'] as int,\n    );\n\nMap<String, dynamic> _$ItemToJson(Item instance) => <String, dynamic>{\n      'id': instance.id,\n      'title': instance.title,\n      'notes': instance.notes,\n      'nextDate': instance.nextDate,\n      'countDown': instance.countDown,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/data/models/item_add_request.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'item_add_request.g.dart';\n\n@JsonSerializable()\nclass ItemAddRequest {\n\n  final String title;\n  final String? notes;\n  final int startDate;\n  final int endDate;\n  final int repeatType;\n  final int interval;\n\n  const ItemAddRequest({\n    required this.title,\n    this.notes,\n    required this.startDate,\n    required this.endDate,\n    required this.repeatType,\n    required this.interval\n  });\n\n  ItemAddRequest copyWith({\n    String? title,\n    String? notes,\n    int? startDate,\n    int? endDate,\n    int? repeatType,\n    int? interval,\n  }) {\n    return ItemAddRequest(\n      title: title ?? this.title,\n      notes: notes ?? this.notes,\n      startDate: startDate ?? this.startDate,\n      endDate: endDate ?? this.endDate,\n      repeatType: repeatType ?? this.repeatType,\n      interval: interval ?? this.interval,\n    );\n  }\n\n  factory ItemAddRequest.fromJson(Map<String, dynamic> json) => _$ItemAddRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$ItemAddRequestToJson(this);\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/data/models/item_add_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'item_add_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nItemAddRequest _$ItemAddRequestFromJson(Map<String, dynamic> json) =>\n    ItemAddRequest(\n      title: json['title'] as String,\n      notes: json['notes'] as String?,\n      startDate: json['startDate'] as int,\n      endDate: json['endDate'] as int,\n      repeatType: json['repeatType'] as int,\n      interval: json['interval'] as int,\n    );\n\nMap<String, dynamic> _$ItemAddRequestToJson(ItemAddRequest instance) =>\n    <String, dynamic>{\n      'title': instance.title,\n      'notes': instance.notes,\n      'startDate': instance.startDate,\n      'endDate': instance.endDate,\n      'repeatType': instance.repeatType,\n      'interval': instance.interval,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/data/models/item_query_request.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'item_query_request.g.dart';\n\n@JsonSerializable()\nclass ItemQueryRequest extends Equatable {\n\n  final int page;\n  final int size;\n  final String sort;\n\n  const ItemQueryRequest({\n    this.page = 1,\n    this.size = 10,\n    this.sort = ''\n  });\n\n  factory ItemQueryRequest.fromJson(Map<String, dynamic> json) => _$ItemQueryRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$ItemQueryRequestToJson(this);\n\n  @override\n  List<Object?> get props => [page, sort];\n\n  ItemQueryRequest copyWith({\n    int? page,\n    int? size,\n    String? sort,\n  }) {\n    return ItemQueryRequest(\n      page: page ?? this.page,\n      size: size ?? this.size,\n      sort: sort ?? this.sort,\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/data/models/item_query_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'item_query_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nItemQueryRequest _$ItemQueryRequestFromJson(Map<String, dynamic> json) =>\n    ItemQueryRequest(\n      page: json['page'] as int? ?? 1,\n      size: json['size'] as int? ?? 10,\n      sort: json['sort'] as String? ?? '',\n    );\n\nMap<String, dynamic> _$ItemQueryRequestToJson(ItemQueryRequest instance) =>\n    <String, dynamic>{\n      'page': instance.page,\n      'size': instance.size,\n      'sort': instance.sort,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/items.dart",
    "content": "export 'data/models/item.dart';\nexport 'data/models/item_add_request.dart';\nexport 'data/models/item_query_request.dart';\nexport 'data/item_repository.dart';\nexport 'bloc/items/items_bloc.dart';\nexport 'bloc/item_form/item_form_bloc.dart';\nexport 'ui/items_page.dart';\nexport 'ui/item_form_page.dart';\nexport 'ui/items_index.dart';\nexport 'ui/item_form/title_input.dart';\nexport 'ui/item_form/date_input.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/ui/item_form/date_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:syncfusion_flutter_datepicker/datepicker.dart';\nimport 'package:intl/intl.dart';\nimport '/components/components.dart';\nimport '/items/items.dart';\n\nclass DateInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<ItemFormBloc, ItemFormState>(\n      buildWhen: (previous, current) => previous.startDate != current.startDate || previous.endDate != current.endDate,\n      builder: (context, state) {\n        return Row(\n          children: [\n            ElevatedButton(\n                onPressed: () async {\n                  final PickerDateRange? range =\n                  await showDialog(\n                      context: context,\n                      builder: (BuildContext context) {\n                        return DateRangePicker(\n                          PickerDateRange(DateTime.fromMillisecondsSinceEpoch(state.startDate!), DateTime.fromMillisecondsSinceEpoch(state.endDate!))\n                        );\n                      }\n                  );\n                  if (range != null) {\n                    BlocProvider.of<ItemFormBloc>(context).add(ItemFormStartDateChanged(range.startDate!.millisecondsSinceEpoch));\n                    BlocProvider.of<ItemFormBloc>(context).add(ItemFormEndDateChanged(range.endDate!.millisecondsSinceEpoch));\n                  }\n                },\n                child: Text('选择日期范围')\n            ),\n            SizedBox(width: 10),\n            Text(\n                DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.startDate!))+\n                ' - '+\n                DateFormat('yyyy.MM.dd').format(DateTime.fromMillisecondsSinceEpoch(state.endDate!))\n            ),\n          ],\n        );\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/ui/item_form/title_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/items/items.dart';\n\nclass TitleInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n          '标题',\n          BlocBuilder<ItemFormBloc, ItemFormState>(\n            buildWhen: (previous, current) => previous.title != current.title,\n            builder: (context, state) {\n              return\n                TextField(\n                  onChanged: (value) => context.read<ItemFormBloc>().add(ItemFormTitleChanged(value)),\n                  decoration: InputDecoration(\n                    contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                    errorText: state.title.invalid ? '请输入标题' : null,\n                  ),\n                );\n            }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/ui/item_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\nimport '/commons/commons.dart';\nimport '/items/items.dart';\n\nclass ItemFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final Item? item;\n\n  const ItemFormPage({\n    required this.type,\n    this.item\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => ItemFormBloc(itemRepository: RepositoryProvider.of<ItemRepository>(context))..add(ItemFormDefaultLoaded(type, item)),\n      child: BlocListener<ItemFormBloc, ItemFormState>(\n        listener: (context, state) {\n          if (state.status == FormzStatus.submissionSuccess) {\n            Message.success('操作成功');\n            Navigator.of(context).pop();\n            BlocProvider.of<ItemsBloc>(context).add(ItemsRefreshed());\n            if (type == 2) {\n              //BlocProvider.of<ItemFetchBloc>(context).add(PayeeFetched());\n            }\n          }\n        },\n        child: Builder(\n          builder: (context) {\n            return Scaffold(\n              appBar: AppBar(\n                centerTitle: true,\n                title: Text(_buildTitle(type)),\n                actions: [\n                  IconButton(\n                    icon: Icon(Icons.done),\n                    onPressed: () {\n                      if (type == 1) {\n                        BlocProvider.of<ItemFormBloc>(context).add(ItemFormAddSubmitted());\n                      } else if (type == 2) {\n                        // BlocProvider.of<ItemFormBloc>(context).add(PayeeFormSubmitted(type, payee));\n                      }\n                    }\n                  )\n                ],\n              ),\n              body: SingleChildScrollView(\n                child: Padding(\n                  padding: EdgeInsets.symmetric(horizontal: 15),\n                  child: Column(\n                    children: [\n                      TitleInput(),\n                      SizedBox(height: 10),\n                      DateInput()\n                    ],\n                  ),\n                ),\n              ),\n            );\n          },\n        ),\n      ),\n\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增项目';\n      case 2:\n        return '修改项目';\n      default:\n        return '类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/ui/items_index.dart",
    "content": "import 'package:flutter/material.dart';\nimport '/commons/commons.dart';\nimport '/items/items.dart';\n\nclass ItemsIndexPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(\n        title: Text('提醒事项'),\n      ),\n      floatingActionButton: FloatingActionButton(\n        onPressed: () {\n          fullDialog(context, ItemFormPage(type: 1));\n        },\n        child: const Icon(Icons.add),\n      ),\n      body: ItemsPage(),\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/items/ui/items_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:pull_to_refresh/pull_to_refresh.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/components/components.dart';\nimport '/items/items.dart';\n\nclass ItemsPage extends StatefulWidget {\n  @override\n  _ItemsPageState createState() => _ItemsPageState();\n}\n\nclass _ItemsPageState extends State<ItemsPage> {\n\n  RefreshController _refreshController = RefreshController(initialRefresh: false);\n\n  @override\n  void initState() {\n    BlocProvider.of<ItemsBloc>(context).add(ItemsRefreshed());\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<ItemsBloc, ItemsState>(\n          listenWhen: (previous, current) => previous.loadMoreStatus != current.loadMoreStatus,\n          listener: (context, state) {\n            if (state.loadMoreStatus == LoadDataStatus.success) {\n              _refreshController.loadComplete();\n            } else if (state.loadMoreStatus == LoadDataStatus.failure) {\n              _refreshController.loadFailed();\n            } else if (state.loadMoreStatus == LoadDataStatus.empty) {\n              _refreshController.loadNoData();\n            }\n          }\n        ),\n        BlocListener<ItemsBloc, ItemsState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              BlocProvider.of<ItemsBloc>(context).add(ItemsRefreshed());\n            }\n          }\n        ),\n      ],\n      child: BlocBuilder<ItemsBloc, ItemsState>(\n        buildWhen: (previous, current) => previous.status != current.status || previous.items != current.items,\n        builder: (context, state) {\n          switch (state.status) {\n            case LoadDataStatus.progress:\n            case LoadDataStatus.initial:\n              return const PageLoading();\n            case LoadDataStatus.success:\n              if (state.items.isEmpty) return Empty();\n              return SmartRefresher(\n                enablePullDown: true,\n                enablePullUp: true,\n                controller: _refreshController,\n                child: _buildList(context, state.items),\n                onRefresh: () async {\n                  BlocProvider.of<ItemsBloc>(context).add(ItemsRefreshed());\n                  _refreshController.refreshCompleted();\n                },\n                onLoading: () async {\n                  BlocProvider.of<ItemsBloc>(context).add(ItemsLoadMore());\n                },\n              );\n            default:\n              return PageError(onTap: () { BlocProvider.of<ItemsBloc>(context).add(ItemsRefreshed()); });\n          }\n\n        },\n      )\n    );\n  }\n\n  Widget _buildList(BuildContext context, List<Item> items) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      itemCount: items.length,\n      itemBuilder: (context, index) {\n        Item item = items[index];\n        return ListTile(\n          dense: true,\n          title: Text(item.title, style: theme.textTheme.bodyText1,),\n          subtitle: Text(dateFormat(item.nextDate), style: theme.textTheme.caption),\n          trailing: Row(\n            mainAxisSize: MainAxisSize.min,\n            children: [\n              Text('还有'+item.countDown.toString()+'天', style: theme.textTheme.bodyText2),\n              Icon(Icons.keyboard_arrow_right)\n            ],\n          ),\n          onTap: () {\n            // Navigator.pushNamed(context, '/flow-detail', arguments: FlowDetailArguments(flow: flow));\n          },\n        );\n      },\n      separatorBuilder: (context, index) {\n        return Divider();\n      },\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/auth/auth_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/login/login.dart';\nimport '/books/books.dart';\n\npart 'auth_event.dart';\npart 'auth_state.dart';\n\nclass AuthBloc extends Bloc<AuthEvent, AuthState> {\n\n  final LoginRepository loginRepository;\n\n  AuthBloc({\n    required this.loginRepository,\n  }) : super(AuthState()) {\n    on<AppStarted>(_onAppStarted);\n    on<LoggedOut>(_onLoggedOut);\n    on<LoggedIn>(_onLoggedIn);\n    on<DefaultBookChanged>(_onDefaultBookChanged);\n  }\n\n  void _onAppStarted(_, Emitter<AuthState> emit) async {\n    await loginRepository.getApiUrl();\n    final String token = await loginRepository.getToken();\n    if (token.isNotEmpty) {\n      try {\n        Session sessionData = await loginRepository.getSession();\n        emit(state.copyWith(\n          status: AuthStatus.authenticated,\n          session: sessionData,\n        ));\n      } catch (_) {\n        print(_);\n        emit(state.copyWith(\n          status: AuthStatus.unauthenticated,\n        ));\n      }\n    } else {\n      emit(state.copyWith(\n        status: AuthStatus.unauthenticated,\n      ));\n    }\n  }\n\n  void _onLoggedOut(_, Emitter<AuthState> emit) async {\n    emit(state.copyWith(\n      status: AuthStatus.loading,\n    ));\n    await loginRepository.deleteToken();\n    emit(state.copyWith(\n      status: AuthStatus.unauthenticated,\n    ));\n  }\n\n  void _onLoggedIn(LoggedIn event, Emitter<AuthState> emit) async {\n    emit(state.copyWith(\n      status: AuthStatus.loading,\n    ));\n    await loginRepository.saveToken(event.token);\n    emit(state.copyWith(\n      status: AuthStatus.authenticated,\n      session: await loginRepository.getSession(),\n    ));\n  }\n\n  void _onDefaultBookChanged(DefaultBookChanged event, Emitter<AuthState> emit) async {\n    emit(state.copyWith(\n      session: state.session!.copyWith(defaultBook: event.book),\n    ));\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/auth/auth_event.dart",
    "content": "part of 'auth_bloc.dart';\n\nabstract class AuthEvent extends Equatable {\n  const AuthEvent();\n  @override\n  List<Object> get props => [];\n}\n\n/// AppStarted will be dispatched when the Flutter application first loads.\n/// It will notify bloc that it needs to determine whether or not there is an existing user.\nclass AppStarted extends AuthEvent {\n  @override\n  String toString() => 'AppStarted';\n}\n\n/// LoggedIn will be dispatched on a successful login.\n/// It will notify the bloc that the user has successfully logged in.\nclass LoggedIn extends AuthEvent {\n  const LoggedIn(this.token);\n  final String token;\n  @override\n  String toString() => 'LoggedIn { token: $token }';\n}\n\n/// LoggedOut will be dispatched on a successful logout.\n/// It will notify the bloc that the user has successfully logged out.\nclass LoggedOut extends AuthEvent {\n  @override\n  String toString() => 'LoggedOut';\n}\n\nclass DefaultBookChanged extends AuthEvent {\n  final Book book;\n  const DefaultBookChanged(this.book);\n  @override\n  List<Object> get props => [book];\n}\n\n\n\n\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/auth/auth_state.dart",
    "content": "part of 'auth_bloc.dart';\n\nenum AuthStatus { uninitialized, authenticated, unauthenticated, loading }\n\nclass AuthState extends Equatable {\n\n  final AuthStatus status;\n  final Session? session;\n\n  const AuthState({\n    this.status = AuthStatus.uninitialized,\n    this.session\n  });\n\n  AuthState copyWith({\n    AuthStatus? status,\n    Session? session,\n  }) {\n    return AuthState(\n      status: status ?? this.status,\n      session: session ?? this.session,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, session];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/login/login_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport '/login/login.dart';\n\npart 'login_event.dart';\npart 'login_state.dart';\n\nclass LoginBloc extends Bloc<LoginEvent, LoginState> {\n\n  LoginBloc({\n    required this.loginRepository,\n    required this.authBloc\n  }) : super(LoginState()) {\n    on<LoginUsernameChanged>(_onUsernameChanged);\n    on<LoginPasswordChanged>(_onPasswordChanged);\n    on<LoginApiUrlChanged>(_onApiUrlChanged);\n    on<LoginButtonPressed>(_onLoginButtonPressed);\n  }\n\n  final LoginRepository loginRepository;\n  final AuthBloc authBloc;\n\n  void _onUsernameChanged(LoginUsernameChanged event, Emitter<LoginState> emit) {\n    final username = Username.dirty(event.username);\n    emit(state.copyWith(\n      username: username,\n      status: Formz.validate([username, state.password, state.apiUrl]),\n    ));\n  }\n\n  void _onPasswordChanged(LoginPasswordChanged event, Emitter<LoginState> emit) {\n    final password = Password.dirty(event.password);\n    emit(state.copyWith(\n      password: password,\n      status: Formz.validate([password, state.username, state.apiUrl]),\n    ));\n  }\n\n  void _onApiUrlChanged(LoginApiUrlChanged event, Emitter<LoginState> emit) {\n    final url = ApiUrl.dirty(event.url);\n    emit(state.copyWith(\n      apiUrl: url,\n      status: Formz.validate([url, state.username, state.password]),\n    ));\n  }\n\n  void _onLoginButtonPressed(_, Emitter<LoginState> emit) async {\n    if (state.status.isValidated) {\n      emit(state.copyWith(status: FormzStatus.submissionInProgress));\n      try {\n        await loginRepository.saveApiUrl(state.apiUrl.value);\n        String token = await loginRepository.logIn(username: state.username.value, password: state.password.value);\n        authBloc.add(LoggedIn(token));\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n      } catch (_) {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/login/login_event.dart",
    "content": "part of 'login_bloc.dart';\n\nabstract class LoginEvent extends Equatable {\n  const LoginEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass LoginUsernameChanged extends LoginEvent {\n  const LoginUsernameChanged(this.username);\n  final String username;\n  @override\n  List<Object> get props => [username];\n}\n\nclass LoginPasswordChanged extends LoginEvent {\n  const LoginPasswordChanged(this.password);\n  final String password;\n  @override\n  List<Object> get props => [password];\n}\n\nclass LoginApiUrlChanged extends LoginEvent {\n  const LoginApiUrlChanged(this.url);\n  final String url;\n  @override\n  List<Object> get props => [url];\n}\n\nclass LoginButtonPressed extends LoginEvent {\n  const LoginButtonPressed();\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/login/login_state.dart",
    "content": "part of 'login_bloc.dart';\n\nclass LoginState extends Equatable {\n\n  final FormzStatus status;\n  final Username username;\n  final Password password;\n  ApiUrl apiUrl;\n\n  LoginState({\n    this.status = FormzStatus.pure,\n    this.username = const Username.pure(),\n    this.password = const Password.pure(),\n    this.apiUrl = const ApiUrl.dirty('http://testjz.jiukuaitech.com/api/v1/'),\n  });\n\n  LoginState copyWith({\n    FormzStatus? status,\n    Username? username,\n    Password? password,\n    ApiUrl? apiUrl,\n  }) {\n    return LoginState(\n      status: status ?? this.status,\n      username: username ?? this.username,\n      password: password ?? this.password,\n      apiUrl: apiUrl ?? this.apiUrl\n    );\n  }\n\n  @override\n  List<Object> get props => [status, username, password, apiUrl];\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/login/models/ApiUrl.dart",
    "content": "import 'package:formz/formz.dart';\n\nenum ApiUrlValidationError { empty }\n\nclass ApiUrl extends FormzInput<String, ApiUrlValidationError> {\n  const ApiUrl.pure() : super.pure('');\n  const ApiUrl.dirty([String value = '']) : super.dirty(value);\n\n  @override\n  ApiUrlValidationError? validator(String? value) {\n    return value?.isNotEmpty == true ? null : ApiUrlValidationError.empty;\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/login/models/password.dart",
    "content": "import 'package:formz/formz.dart';\n\nenum PasswordValidationError { empty }\n\nclass Password extends FormzInput<String, PasswordValidationError> {\n  const Password.pure() : super.pure('');\n  const Password.dirty([String value = '']) : super.dirty(value);\n\n  @override\n  PasswordValidationError? validator(String? value) {\n    return value?.isNotEmpty == true ? null : PasswordValidationError.empty;\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/bloc/login/models/username.dart",
    "content": "import 'package:formz/formz.dart';\n\nenum UsernameValidationError { empty }\n\nclass Username extends FormzInput<String, UsernameValidationError> {\n  const Username.pure() : super.pure('');\n  const Username.dirty([String value = '']) : super.dirty(value);\n\n  @override\n  UsernameValidationError? validator(String? value) {\n    return value?.isNotEmpty == true ? null : UsernameValidationError.empty;\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/data/login_repository.dart",
    "content": "import 'dart:convert';\nimport 'package:shared_preferences/shared_preferences.dart';\n\nimport '/login/login.dart';\nimport '/commons/commons.dart';\n\nclass LogInFailure implements Exception { }\n\nclass LoginRepository {\n\n  Future<String> logIn({\n    required String username,\n    required String password\n  }) async {\n    String response = await HttpClient().post('signin', data: {'userName': username, 'password': password});\n    var responseDecoded = json.decode(response);\n    if (responseDecoded['success']) {\n      return responseDecoded['data']['token'];\n    } else {\n      throw LogInFailure();\n    }\n  }\n\n  Future<Session> getSession() async {\n    String response = await HttpClient().get('session');\n    // TODO response 可能失败\n    return Session.fromJson(json.decode(response)['data']);\n  }\n\n  Future<bool> saveToken(String token) async {\n    session[\"userToken\"] = token;\n    final SharedPreferences prefs = await SharedPreferences.getInstance();\n    return prefs.setString('User-Token', token);\n  }\n\n  Future<bool> saveApiUrl(String url) async {\n    session[\"apiUrl\"] = url;\n    final SharedPreferences prefs = await SharedPreferences.getInstance();\n    HttpClient().init();\n    return prefs.setString('Api-Url', url);\n  }\n\n  Future<bool> deleteToken() async {\n    session[\"userToken\"] = null;\n    final SharedPreferences prefs = await SharedPreferences.getInstance();\n    return prefs.remove('User-Token');\n  }\n\n  Future<String> getToken() async {\n    final SharedPreferences prefs = await SharedPreferences.getInstance();\n    final String userToken = prefs.getString('User-Token') ?? '';\n    session['userToken'] = userToken;\n    return userToken;\n  }\n\n  Future<String> getApiUrl() async {\n    final SharedPreferences prefs = await SharedPreferences.getInstance();\n    final String apiUrl = prefs.getString('Api-Url') ?? '';\n    session['apiUrl'] = apiUrl;\n    return apiUrl;\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/data/models/session.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\nimport '/books/books.dart';\nimport '/groups/groups.dart';\nimport 'user.dart';\n\npart 'session.g.dart';\n\n@JsonSerializable()\nclass Session extends Equatable {\n\n  final User userSessionVO;\n  final Book defaultBook;\n  final Group defaultGroup;\n\n  const Session({\n    required this.userSessionVO,\n    required this.defaultBook,\n    required this.defaultGroup\n  });\n\n  Session copyWith({\n    User? userSessionVO,\n    Book? defaultBook,\n    Group? defaultGroup\n  }) {\n    return Session(\n      userSessionVO: userSessionVO ?? this.userSessionVO,\n      defaultBook: defaultBook ?? this.defaultBook,\n      defaultGroup: defaultGroup ?? this.defaultGroup\n    );\n  }\n\n  factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json);\n\n  Map<String, dynamic> toJson() => _$SessionToJson(this);\n\n  @override\n  List<Object> get props => [userSessionVO, defaultBook, defaultGroup];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/data/models/session.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'session.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nSession _$SessionFromJson(Map<String, dynamic> json) => Session(\n      userSessionVO:\n          User.fromJson(json['userSessionVO'] as Map<String, dynamic>),\n      defaultBook: Book.fromJson(json['defaultBook'] as Map<String, dynamic>),\n      defaultGroup:\n          Group.fromJson(json['defaultGroup'] as Map<String, dynamic>),\n    );\n\nMap<String, dynamic> _$SessionToJson(Session instance) => <String, dynamic>{\n      'userSessionVO': instance.userSessionVO,\n      'defaultBook': instance.defaultBook,\n      'defaultGroup': instance.defaultGroup,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/data/models/user.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'user.g.dart';\n\n@JsonSerializable()\nclass User extends Equatable {\n  final int id;\n  final String userName;\n  final int vipTime;\n\n  const User({\n    required this.id,\n    required this.userName,\n    required this.vipTime,\n  });\n\n  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);\n\n  Map<String, dynamic> toJson() => _$UserToJson(this);\n\n  @override\n  List<Object> get props => [id];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/data/models/user.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'user.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nUser _$UserFromJson(Map<String, dynamic> json) => User(\n      id: json['id'] as int,\n      userName: json['userName'] as String,\n      vipTime: json['vipTime'] as int,\n    );\n\nMap<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{\n      'id': instance.id,\n      'userName': instance.userName,\n      'vipTime': instance.vipTime,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/login.dart",
    "content": "export 'bloc/login/login_bloc.dart';\nexport 'bloc/login/models/password.dart';\nexport 'bloc/login/models/username.dart';\nexport 'bloc/login/models/ApiUrl.dart';\nexport 'bloc/auth/auth_bloc.dart';\nexport 'data/login_repository.dart';\nexport 'data/models/user.dart';\nexport 'data/models/session.dart';\nexport 'ui/login_page.dart';\nexport 'ui/widgets/login_form.dart';\nexport 'ui/widgets/user_name_input.dart';\nexport 'ui/widgets/password_input.dart';\nexport 'ui/widgets/api_url_input.dart';\nexport 'ui/widgets/submit_btn.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/ui/login_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/login/login.dart';\n\nclass LoginPage extends StatelessWidget {\n\n  @override\n  Widget build(BuildContext context) {\n    return Material(\n      child: SingleChildScrollView(\n        child: Padding(\n          padding: EdgeInsets.symmetric(horizontal: 15),\n          child: Column(\n            children: [\n              Container(\n                  width: 200,\n                  padding: EdgeInsets.only(top: 50.0, bottom: 60.0),\n                  child: Image.asset('assets/images/logo.png')\n              ),\n              BlocProvider(\n                create: (_) => LoginBloc(\n                    loginRepository: RepositoryProvider.of<LoginRepository>(context),\n                    authBloc: BlocProvider.of<AuthBloc>(context)\n                ),\n                child: LoginForm(),\n              ),\n              SizedBox(height: 50),\n            ],\n          )\n        ),\n      )\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/ui/widgets/api_url_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/login/login.dart';\n\nclass ApiUrlInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<LoginBloc, LoginState>(\n      buildWhen: (previous, current) => previous.apiUrl != current.apiUrl,\n      builder: (context, state) {\n        return TextFormField(\n          initialValue: state.apiUrl.value,\n          onChanged: (url) => context.read<LoginBloc>().add(LoginApiUrlChanged(url)),\n          decoration: InputDecoration(\n            hintText: 'API地址',\n            contentPadding: EdgeInsets.symmetric(horizontal: 10),\n            errorText: state.apiUrl.invalid ? '请输入API地址' : null,\n          ),\n        );\n      },\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/ui/widgets/login_form.dart",
    "content": "import 'package:bookkeeping_user_flutter/login/ui/widgets/api_url_input.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\n\nimport '/commons/commons.dart';\nimport '/login/login.dart';\n\nclass LoginForm extends StatelessWidget {\n  Widget build(BuildContext context) {\n    return BlocListener<LoginBloc, LoginState>(\n      listener: (context, state) {\n        if (state.status == FormzStatus.submissionSuccess) {\n          Message.success('登录成功');\n        }\n      },\n      child: Column(\n        children: [\n          UsernameInput(),\n          SizedBox(height: 10),\n          PasswordInput(),\n          SizedBox(height: 10),\n          ApiUrlInput(),\n          SizedBox(height: 30),\n          SizedBox(\n            width: double.infinity,\n            height: 50,\n            child: SubmitBtn(),\n          )\n        ],\n      )\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/ui/widgets/password_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/login/login.dart';\n\nclass PasswordInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<LoginBloc, LoginState>(\n      buildWhen: (previous, current) => previous.password != current.password,\n      builder: (context, state) {\n        return TextField(\n          key: const Key('loginForm_passwordInput_textField'),\n          onChanged: (password) => context.read<LoginBloc>().add(LoginPasswordChanged(password)),\n          obscureText: true,\n          decoration: InputDecoration(\n            hintText: '密码',\n            contentPadding: EdgeInsets.symmetric(horizontal: 10),\n            errorText: state.password.invalid ? '请输入密码' : null,\n          ),\n        );\n      },\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/ui/widgets/submit_btn.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\n\nimport '/login/login.dart';\n\nclass SubmitBtn extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<LoginBloc, LoginState>(\n      buildWhen: (previous, current) => previous.status != current.status,\n      builder: (context, state) {\n        return ElevatedButton(\n          onPressed: state.status.isValidated && !state.status.isSubmissionInProgress ?\n              () {context.read<LoginBloc>().add(LoginButtonPressed());} : null,\n          child: state.status.isSubmissionInProgress ?\n            CircularProgressIndicator(\n              valueColor: AlwaysStoppedAnimation<Color>(Colors.white),\n              strokeWidth: 1.5,\n            ) : Text('登录')\n        );\n      },\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/login/ui/widgets/user_name_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/login/login.dart';\n\nclass UsernameInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<LoginBloc, LoginState>(\n      buildWhen: (previous, current) => previous.username != current.username,\n      builder: (context, state) {\n        return TextField(\n          onChanged: (username) => context.read<LoginBloc>().add(LoginUsernameChanged(username)),\n          decoration: InputDecoration(\n            hintText: '用户名',\n            contentPadding: EdgeInsets.symmetric(horizontal: 10),\n            errorText: state.username.invalid ? '请输入用户名' : null,\n          ),\n        );\n      },\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/main.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport '/categories/categories.dart';\nimport '/flows/flows.dart';\nimport '/payees/payees.dart';\nimport '/tags/tags.dart';\nimport '/accounts/accounts.dart';\nimport '/login/login.dart';\nimport '/books/books.dart';\nimport '/charts/charts.dart';\nimport '/currency/currency.dart';\nimport '/items/items.dart';\nimport 'app.dart';\n\nvoid main() {\n  // BlocOverrides.runZoned(\n  //   () {\n  //     runApp(App(\n  //       loginRepository: LoginRepository(),\n  //     ));\n  //   },\n  //   blocObserver: AppBlocObserver(),\n  // );\n  runApp(App(\n    loginRepository: LoginRepository(),\n    accountRepository: AccountRepository(),\n    categoryRepository: CategoryRepository(),\n    payeeRepository: PayeeRepository(),\n    tagRepository: TagRepository(),\n    flowRepository: FlowRepository(),\n    bookRepository: BookRepository(),\n    reportRepository: ReportRepository(),\n    itemRepository: ItemRepository(),\n    currencyRepository: CurrencyRepository(),\n  ));\n\n  // runApp(new MaterialApp(home: PositionedTiles()));\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/my/my.dart",
    "content": "export 'my_page.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/my/my_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:intl/intl.dart';\n\nimport '/commons/commons.dart';\nimport '/login/login.dart';\n\nclass MyPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state = context.watch<AuthBloc>().state;\n    return Scaffold(\n        appBar: AppBar(\n          title: const Text('我的'),\n          centerTitle: true,\n        ),\n        body: SingleChildScrollView(\n          child: Column(\n            mainAxisAlignment: MainAxisAlignment.center,\n            crossAxisAlignment: CrossAxisAlignment.center,\n            children: [\n              ListTile(\n                title: Text('登录用户名：'),\n                trailing: Text(state.session?.userSessionVO.userName ?? '')\n              ),\n              ListTile(\n                title: Text('会员到期日：'),\n                trailing: Text(DateFormat('yyyy-MM-dd').format(DateTime.fromMillisecondsSinceEpoch(state.session?.userSessionVO.vipTime ?? 0)))\n              ),\n              ListTile(\n                title: Text('当前组：'),\n                trailing: Text(state.session?.defaultGroup.name ?? '')\n              ),\n              ListTile(\n                title: Text('当前账本：'),\n                trailing: Text(state.session?.defaultBook.name ?? '')\n              ),\n              Divider(),\n              ListTile(\n                title: Text('账本管理'),\n                trailing: Icon(Icons.keyboard_arrow_right),\n                onTap: () {\n                  Navigator.pushNamed(context, '/books');\n                },\n              ),\n              Divider(),\n              ListTile(\n                title: const Text('支出类别'),\n                trailing: Icon(Icons.keyboard_arrow_right),\n                onTap: () {\n                  Navigator.pushNamed(context, '/expense-categories');\n                }\n              ),\n              ListTile(\n                title: const Text('收入类别'),\n                trailing: Icon(Icons.keyboard_arrow_right),\n                onTap: () {\n                  Navigator.pushNamed(context, '/income-categories');\n                }\n              ),\n              ListTile(\n                title: const Text('交易标签'),\n                trailing: Icon(Icons.keyboard_arrow_right),\n                onTap: () {\n                  Navigator.pushNamed(context, '/tags');\n                }\n              ),\n              ListTile(\n                title: const Text('交易对象'),\n                trailing: Icon(Icons.keyboard_arrow_right),\n                onTap: () {\n                  Navigator.pushNamed(context, '/payees');\n                }\n              ),\n              Divider(),\n              ListTile(\n                title: const Text('提醒事项'),\n                trailing: Icon(Icons.keyboard_arrow_right),\n                onTap: () {\n                  Navigator.pushNamed(context, '/items-index');\n                }\n              ),\n              Divider(),\n              ListTile(\n                  title: const Text('api地址'),\n                  trailing: Text(session['apiUrl']),\n                  onTap: () {\n                    //Navigator.pushNamed(context, );\n                  }\n              ),\n              Divider(),\n              ListTile(\n                title: const Text('使用指南'),\n                trailing: Icon(Icons.keyboard_arrow_right),\n                onTap: () {\n                  Navigator.of(context).push(\n                    MaterialPageRoute(\n                      builder: (context) => const WebViewPage(title: '使用指南', url: 'https://docs.jz.jiukuaitech.com/'),\n                    ),\n                  );\n                }\n              ),\n              Divider(),\n              ListTile(\n                  title: const Text('当前版本号：'),\n                  trailing: const Text('1.0.2')\n              ),\n              Divider(),\n              Container(\n                width: double.infinity,\n                padding: EdgeInsets.symmetric(horizontal: 15),\n                child: ElevatedButton(\n                    child: const Text('退出登录'),\n                    onPressed: () async {\n                      if (await confirm(\n                        context,\n                        content: Text('确定退出吗？'),\n                        textOK: Text('确定'),\n                        textCancel: Text('取消'),\n                      )) {\n                        BlocProvider.of<AuthBloc>(context).add(LoggedOut());\n                      }\n                    }\n                )\n              )\n            ],\n          ),\n        )\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/observer.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:flutter/cupertino.dart';\n\nclass AppBlocObserver extends BlocObserver {\n\n  @override\n  void onEvent(Bloc bloc, Object? event) {\n    super.onEvent(bloc, event);\n    print(event);\n  }\n\n  @override\n  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {\n    print(error);\n    super.onError(bloc, error, stackTrace);\n  }\n\n  @override\n  void onChange(BlocBase bloc, Change change) {\n    super.onChange(bloc, change);\n    print(change);\n  }\n\n  @override\n  void onTransition(Bloc bloc, Transition transition) {\n    super.onTransition(bloc, transition);\n    print(transition);\n  }\n}\n\nclass AppNavigatorObserver extends NavigatorObserver {\n\n  @override\n  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {\n    print(\"${route.settings.name} pushed\");\n  }\n\n  @override\n  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {\n    print(\"${route.settings.name} popped\");\n  }\n\n  @override\n  void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {\n    print(\"${oldRoute!.settings.name} is replaced by ${newRoute!.settings.name}\");\n  }\n\n  @override\n  void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {\n    print(\"${route.settings.name} removed\");\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_enable/payee_enable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/payees/payees.dart';\n\npart 'payee_enable_event.dart';\npart 'payee_enable_state.dart';\n\nclass PayeeEnableBloc extends Bloc<PayeeEnableEvent, PayeeEnableState> {\n\n  final PayeeRepository payeeRepository;\n\n  PayeeEnableBloc({\n    required this.payeeRepository\n  }) : super(PayeeEnableStateLoadInProgress()) {\n    on<PayeeEnableLoaded>(_onPayeeEnableLoaded);\n  }\n\n  void _onPayeeEnableLoaded(_, Emitter<PayeeEnableState> emit) async {\n    // 只加载一次数据\n    // if (state is PayeeEnableStateLoadSuccess) return;\n    try {\n      emit(PayeeEnableStateLoadInProgress());\n      final payees = await payeeRepository.getEnable();\n      emit(PayeeEnableStateLoadSuccess(payees));\n    } catch (_) {\n      emit(PayeeEnableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_enable/payee_enable_event.dart",
    "content": "part of 'payee_enable_bloc.dart';\n\nabstract class PayeeEnableEvent extends Equatable {\n\n  const PayeeEnableEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass PayeeEnableLoaded extends PayeeEnableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_enable/payee_enable_state.dart",
    "content": "part of 'payee_enable_bloc.dart';\n\nabstract class PayeeEnableState extends Equatable {\n\n  const PayeeEnableState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass PayeeEnableStateLoadInProgress extends PayeeEnableState { }\n\nclass PayeeEnableStateLoadSuccess extends PayeeEnableState {\n\n  final List<Payee> payees;\n\n  const PayeeEnableStateLoadSuccess(this.payees);\n\n  @override\n  List<Object> get props => [payees];\n\n}\n\nclass PayeeEnableStateLoadFailure extends PayeeEnableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_expenseable/payee_expenseable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/payees/payees.dart';\n\npart 'payee_expenseable_event.dart';\npart 'payee_expenseable_state.dart';\n\nclass PayeeExpenseableBloc extends Bloc<PayeeExpenseableEvent, PayeeExpenseableState> {\n\n  final PayeeRepository payeeRepository;\n\n  PayeeExpenseableBloc({\n    required this.payeeRepository\n  }) : super(PayeeExpenseableStateLoadInProgress()) {\n    on<PayeeExpenseableLoaded>(_onPayeeExpenseableLoaded);\n  }\n\n  void _onPayeeExpenseableLoaded(_, Emitter<PayeeExpenseableState> emit) async {\n    // 只加载一次数据\n    // if (state is PayeeExpenseableStateLoadSuccess) return;\n    try {\n      emit(PayeeExpenseableStateLoadInProgress());\n      final payees = await payeeRepository.getExpenseable();\n      emit(PayeeExpenseableStateLoadSuccess(payees));\n    } catch (_) {\n      emit(PayeeExpenseableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_expenseable/payee_expenseable_event.dart",
    "content": "part of 'payee_expenseable_bloc.dart';\n\nabstract class PayeeExpenseableEvent extends Equatable {\n\n  const PayeeExpenseableEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass PayeeExpenseableLoaded extends PayeeExpenseableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_expenseable/payee_expenseable_state.dart",
    "content": "part of 'payee_expenseable_bloc.dart';\n\nabstract class PayeeExpenseableState extends Equatable {\n\n  const PayeeExpenseableState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass PayeeExpenseableStateLoadInProgress extends PayeeExpenseableState { }\n\nclass PayeeExpenseableStateLoadSuccess extends PayeeExpenseableState {\n\n  final List<Payee> payees;\n\n  const PayeeExpenseableStateLoadSuccess(this.payees);\n\n  @override\n  List<Object> get props => [payees];\n\n}\n\nclass PayeeExpenseableStateLoadFailure extends PayeeExpenseableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_fetch/payee_fetch_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\n\nimport '/payees/payees.dart';\nimport '/commons/commons.dart';\n\npart 'payee_fetch_event.dart';\npart 'payee_fetch_state.dart';\n\nclass PayeeFetchBloc extends Bloc<PayeeFetchEvent, PayeeFetchState> {\n\n  final PayeeRepository payeeRepository;\n\n  PayeeFetchBloc({\n    required this.payeeRepository,\n  }) : super(PayeeFetchState()) {\n    on<PayeeFetched>(_onFetched);\n    on<PayeeLoadDefault>(_onDefault);\n  }\n\n  void _onDefault(PayeeLoadDefault event, Emitter<PayeeFetchState> emit) {\n    emit(state.copyWith(\n      status: LoadDataStatus.success,\n      payee: event.payee\n    ));\n  }\n\n  void _onFetched(_, Emitter<PayeeFetchState> emit) async {\n    try {\n      emit(state.copyWith(status: LoadDataStatus.progress));\n      final payee = await payeeRepository.get(state.payee!.id);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        payee: payee,\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_fetch/payee_fetch_event.dart",
    "content": "part of 'payee_fetch_bloc.dart';\n\n@immutable\nclass PayeeFetchEvent extends Equatable {\n  const PayeeFetchEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass PayeeFetched extends PayeeFetchEvent {}\n\nclass PayeeLoadDefault extends PayeeFetchEvent {\n  final Payee payee;\n  const PayeeLoadDefault({\n    required this.payee,\n  });\n  List<Object> get props => [payee];\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_fetch/payee_fetch_state.dart",
    "content": "part of 'payee_fetch_bloc.dart';\n\n@immutable\nclass PayeeFetchState extends Equatable {\n\n  final LoadDataStatus status;\n  final Payee? payee;\n\n  const PayeeFetchState({\n    this.status = LoadDataStatus.initial,\n    this.payee,\n  });\n\n  PayeeFetchState copyWith({\n    LoadDataStatus? status,\n    Payee? payee,\n  }) {\n    return PayeeFetchState(\n      status: status ?? this.status,\n      payee: payee ?? this.payee,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, payee];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_form/payee_form_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:bookkeeping_user_flutter/payees/payees.dart';\nimport 'package:bookkeeping_user_flutter/payees/data/models/payee_form_request.dart';\nimport 'package:bookkeeping_user_flutter/commons/commons.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport 'package:meta/meta.dart';\n\npart 'payee_form_event.dart';\npart 'payee_form_state.dart';\n\nclass PayeeFormBloc extends Bloc<PayeeFormEvent, PayeeFormState> {\n\n  final PayeeRepository payeeRepository;\n\n  PayeeFormBloc({\n    required this.payeeRepository,\n  }) : super(const PayeeFormState()) {\n    on<PayeeFormNameChanged>(_onNameChanged);\n    on<PayeeFormExpenseableChanged>(_onExpenseableChanged);\n    on<PayeeFormIncomeableChanged>(_onIncomeableChanged);\n    on<PayeeFormNotesChanged>(_onNotesChanged);\n    on<PayeeFormSubmitted>(_onSubmitted);\n    on<PayeeFormDefaultLoaded>(_onDefaultLoaded);\n  }\n\n  void _onNameChanged(PayeeFormNameChanged event, Emitter<PayeeFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(name: event.name),\n    ));\n  }\n\n  void _onExpenseableChanged(PayeeFormExpenseableChanged event, Emitter<PayeeFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(expenseable: event.expenseable),\n    ));\n  }\n\n  void _onIncomeableChanged(PayeeFormIncomeableChanged event, Emitter<PayeeFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(incomeable: event.incomeable),\n    ));\n  }\n\n  void _onNotesChanged(PayeeFormNotesChanged event, Emitter<PayeeFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(notes: event.notes),\n    ));\n  }\n\n  void _onDefaultLoaded(PayeeFormDefaultLoaded event, Emitter<PayeeFormState> emit) {\n    emit(state.copyWith(\n      status: FormzStatus.pure,\n      request: state.request.copyWith(\n        name: event.payee?.name ?? '',\n        expenseable: event.payee?.expenseable ?? true,\n        incomeable: event.payee?.incomeable ?? false,\n        notes: event.payee?.notes ?? '',\n      )\n    ));\n  }\n\n  void _onSubmitted(PayeeFormSubmitted event, Emitter<PayeeFormState> emit) async {\n    try {\n      bool result = false;\n      switch (event.type) {\n        case 1:\n          result = await payeeRepository.add(state.request);\n          break;\n        case 2:\n          result = await payeeRepository.update(event.payee!.id, state.request);\n          break;\n        default:\n          break;\n      }\n      if (result) {\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n      } else {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    } catch (_) {\n      emit(state.copyWith(status: FormzStatus.submissionFailure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_form/payee_form_event.dart",
    "content": "part of 'payee_form_bloc.dart';\n\n@immutable\nabstract class PayeeFormEvent extends Equatable {\n  const PayeeFormEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass PayeeFormNameChanged extends PayeeFormEvent {\n  const PayeeFormNameChanged(this.name);\n  final String name;\n  @override\n  List<Object> get props => [name];\n}\n\nclass PayeeFormExpenseableChanged extends PayeeFormEvent {\n  const PayeeFormExpenseableChanged(this.expenseable);\n  final bool expenseable;\n  @override\n  List<Object> get props => [expenseable];\n}\n\nclass PayeeFormIncomeableChanged extends PayeeFormEvent {\n  const PayeeFormIncomeableChanged(this.incomeable);\n  final bool incomeable;\n  @override\n  List<Object> get props => [incomeable];\n}\n\nclass PayeeFormNotesChanged extends PayeeFormEvent {\n  const PayeeFormNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass PayeeFormDefaultLoaded extends PayeeFormEvent {\n  final int type;\n  final Payee? payee;\n  const PayeeFormDefaultLoaded(this.type, this.payee);\n  @override\n  List<Object?> get props => [type, payee];\n}\n\nclass PayeeFormSubmitted extends PayeeFormEvent {\n  final int type;\n  final Payee? payee;\n  const PayeeFormSubmitted(this.type, this.payee);\n  @override\n  List<Object?> get props => [type, payee];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_form/payee_form_state.dart",
    "content": "part of 'payee_form_bloc.dart';\n\n@immutable\nclass PayeeFormState extends Equatable {\n\n  final FormzStatus status;\n  final PayeeFormRequest request;\n\n  const PayeeFormState({\n    this.status = FormzStatus.pure,\n    this.request = const PayeeFormRequest(),\n  });\n\n  PayeeFormState copyWith({\n    FormzStatus? status,\n    PayeeFormRequest? request,\n  }) {\n    return PayeeFormState(\n      status: status ?? this.status,\n      request: request ?? this.request,\n    );\n  }\n\n  @override\n  List<Object> get props => [status, request];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_incomeable/payee_incomeable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/payees/payees.dart';\n\npart 'payee_incomeable_event.dart';\npart 'payee_incomeable_state.dart';\n\nclass PayeeIncomeableBloc extends Bloc<PayeeIncomeableEvent, PayeeIncomeableState> {\n\n  final PayeeRepository payeeRepository;\n\n  PayeeIncomeableBloc({\n    required this.payeeRepository\n  }) : super(PayeeIncomeableStateLoadInProgress()) {\n    on<PayeeIncomeableLoaded>(_onPayeeIncomeableLoaded);\n  }\n\n  void _onPayeeIncomeableLoaded(_, Emitter<PayeeIncomeableState> emit) async {\n    // 只加载一次数据\n    // if (state is PayeeIncomeableStateLoadSuccess) return;\n    try {\n      emit(PayeeIncomeableStateLoadInProgress());\n      final payees = await payeeRepository.getIncomeable();\n      emit(PayeeIncomeableStateLoadSuccess(payees));\n    } catch (_) {\n      emit(PayeeIncomeableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_incomeable/payee_incomeable_event.dart",
    "content": "part of 'payee_incomeable_bloc.dart';\n\nabstract class PayeeIncomeableEvent extends Equatable {\n\n  const PayeeIncomeableEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass PayeeIncomeableLoaded extends PayeeIncomeableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payee_incomeable/payee_incomeable_state.dart",
    "content": "part of 'payee_incomeable_bloc.dart';\n\nabstract class PayeeIncomeableState extends Equatable {\n\n  const PayeeIncomeableState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass PayeeIncomeableStateLoadInProgress extends PayeeIncomeableState { }\n\nclass PayeeIncomeableStateLoadSuccess extends PayeeIncomeableState {\n\n  final List<Payee> payees;\n\n  const PayeeIncomeableStateLoadSuccess(this.payees);\n\n  @override\n  List<Object> get props => [payees];\n\n}\n\nclass PayeeIncomeableStateLoadFailure extends PayeeIncomeableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payees/payees_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\n\nimport '/commons/commons.dart';\nimport '/payees/payees.dart';\n\npart 'payees_event.dart';\npart 'payees_state.dart';\n\nclass PayeesBloc extends Bloc<PayeesEvent, PayeesState> {\n\n  final PayeeRepository payeeRepository;\n\n  PayeesBloc({\n    required this.payeeRepository,\n  }) : super(PayeesState()) {\n    on<PayeesRefreshed>(_onRefreshed);\n    on<PayeesLoadMore>(_onLoadMore);\n    on<PayeesSortChanged>(_onSortChanged);\n    on<PayeeDeleted>(_onDeleted);\n    on<PayeeToggled>(_onToggled);\n  }\n\n  void _onRefreshed(_, Emitter<PayeesState> emit) async {\n    try {\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n        request: state.request.copyWith(page: 1),\n      ));\n      final payees = await payeeRepository.query(state.request);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        payees: payees,\n        request: state.request.copyWith(page: state.request.page + 1),\n      ));\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onLoadMore(_, Emitter<PayeesState> emit) async {\n    try {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.progress));\n      final payees = await payeeRepository.query(state.request);\n      if (payees.isNotEmpty) {\n        emit(state.copyWith(\n          request: state.request.copyWith(page: state.request.page + 1),\n          payees: List.of(state.payees)..addAll(payees),\n          loadMoreStatus: LoadDataStatus.success,\n        ));\n      } else {\n        emit(state.copyWith(loadMoreStatus: LoadDataStatus.empty));\n      }\n    } catch (_) {\n      emit(state.copyWith(loadMoreStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onSortChanged(PayeesSortChanged event, Emitter<PayeesState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(sort: event.sort),\n    ));\n  }\n\n  void _onDeleted(PayeeDeleted event, Emitter<PayeesState> emit) async {\n    try {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.progress));\n      final result = await payeeRepository.delete(event.id);\n      if (result) {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onToggled(PayeeToggled event, Emitter<PayeesState> emit) async {\n    try {\n      emit(state.copyWith(toggleStatus: LoadDataStatus.progress));\n      final result = await payeeRepository.toggle(event.id);\n      if (result) {\n        emit(state.copyWith(toggleStatus: LoadDataStatus.success));\n      } else {\n        emit(state.copyWith(toggleStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(toggleStatus: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payees/payees_event.dart",
    "content": "part of 'payees_bloc.dart';\n\n@immutable\nabstract class PayeesEvent extends Equatable {\n  const PayeesEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass PayeesRefreshed extends PayeesEvent {}\n\nclass PayeesLoadMore extends PayeesEvent {}\n\nclass PayeeDeleted extends PayeesEvent {\n  final String id;\n  const PayeeDeleted(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass PayeeToggled extends PayeesEvent {\n  final String id;\n  const PayeeToggled(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass PayeesSortChanged extends PayeesEvent {\n  const PayeesSortChanged(this.sort);\n  final String sort;\n  @override\n  List<Object> get props => [sort];\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/bloc/payees/payees_state.dart",
    "content": "part of 'payees_bloc.dart';\n\n@immutable\nclass PayeesState extends Equatable {\n\n  final LoadDataStatus status;\n  final List<Payee> payees;\n  final PayeeQueryRequest request;\n  final LoadDataStatus loadMoreStatus;\n  final LoadDataStatus deleteStatus;\n  final LoadDataStatus toggleStatus;\n\n  const PayeesState({\n    this.status = LoadDataStatus.initial,\n    this.payees = const <Payee>[],\n    this.request = const PayeeQueryRequest(),\n    this.loadMoreStatus = LoadDataStatus.initial,\n    this.deleteStatus = LoadDataStatus.initial,\n    this.toggleStatus = LoadDataStatus.initial,\n  });\n\n\n  @override\n  List<Object> get props => [status, payees, request, loadMoreStatus, deleteStatus, toggleStatus];\n\n  PayeesState copyWith({\n    LoadDataStatus? status,\n    List<Payee>? payees,\n    PayeeQueryRequest? request,\n    LoadDataStatus? loadMoreStatus,\n    LoadDataStatus? deleteStatus,\n    LoadDataStatus? toggleStatus,\n  }) {\n    return PayeesState(\n      status: status ?? this.status,\n      payees: payees ?? this.payees,\n      request: request ?? this.request,\n      loadMoreStatus: loadMoreStatus ?? this.loadMoreStatus,\n      deleteStatus: deleteStatus ?? this.deleteStatus,\n      toggleStatus: toggleStatus ?? this.toggleStatus,\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/data/models/payee.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'payee.g.dart';\n\n@JsonSerializable()\nclass Payee extends Equatable {\n\n  final int id;\n  final String name;\n  final bool enable;\n  final bool expenseable;\n  final bool incomeable;\n  final String? notes;\n\n  Payee({\n    required this.id,\n    required this.name,\n    required this.enable,\n    required this.expenseable,\n    required this.incomeable,\n    this.notes\n  });\n\n  factory Payee.fromJson(Map<String, dynamic> json) => _$PayeeFromJson(json);\n\n  Map<String, dynamic> toJson() => _$PayeeToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/data/models/payee.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'payee.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nPayee _$PayeeFromJson(Map<String, dynamic> json) => Payee(\n      id: json['id'] as int,\n      name: json['name'] as String,\n      enable: json['enable'] as bool,\n      expenseable: json['expenseable'] as bool,\n      incomeable: json['incomeable'] as bool,\n      notes: json['notes'] as String?,\n    );\n\nMap<String, dynamic> _$PayeeToJson(Payee instance) => <String, dynamic>{\n      'id': instance.id,\n      'name': instance.name,\n      'enable': instance.enable,\n      'expenseable': instance.expenseable,\n      'incomeable': instance.incomeable,\n      'notes': instance.notes,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/data/models/payee_form_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'payee_form_request.g.dart';\n\n@JsonSerializable()\nclass PayeeFormRequest extends Equatable {\n\n  final String? name;\n  final bool? expenseable;\n  final bool? incomeable;\n  final String? notes;\n\n  const PayeeFormRequest({\n    this.name,\n    this.expenseable,\n    this.incomeable,\n    this.notes,\n  });\n\n  PayeeFormRequest copyWith({\n    String? name,\n    bool? expenseable,\n    bool? incomeable,\n    String? notes,\n  }) {\n    return PayeeFormRequest(\n      name: name ?? this.name,\n      expenseable: expenseable ?? this.expenseable,\n      incomeable: incomeable ?? this.incomeable,\n      notes: notes ?? this.notes,\n    );\n  }\n\n  Map<String, dynamic> toJson() => _$PayeeFormRequestToJson(this);\n\n  @override\n  List<Object?> get props => [name, expenseable, incomeable, notes];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/data/models/payee_form_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'payee_form_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nPayeeFormRequest _$PayeeFormRequestFromJson(Map<String, dynamic> json) =>\n    PayeeFormRequest(\n      name: json['name'] as String?,\n      expenseable: json['expenseable'] as bool?,\n      incomeable: json['incomeable'] as bool?,\n      notes: json['notes'] as String?,\n    );\n\nMap<String, dynamic> _$PayeeFormRequestToJson(PayeeFormRequest instance) =>\n    <String, dynamic>{\n      'name': instance.name,\n      'expenseable': instance.expenseable,\n      'incomeable': instance.incomeable,\n      'notes': instance.notes,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/data/models/payee_query_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'payee_query_request.g.dart';\n\n@JsonSerializable()\nclass PayeeQueryRequest extends Equatable {\n\n  final int page;\n  final int size;\n  final String sort;\n\n  const PayeeQueryRequest({\n    this.page = 1,\n    this.size = 15,\n    this.sort = 'id,desc'\n  });\n\n  factory PayeeQueryRequest.fromJson(Map<String, dynamic> json) => _$PayeeQueryRequestFromJson(json);\n\n  Map<String, dynamic> toJson() => _$PayeeQueryRequestToJson(this);\n\n  PayeeQueryRequest copyWith({\n    int? page,\n    int? size,\n    String? sort,\n  }) {\n    return PayeeQueryRequest(\n      page: page ?? this.page,\n      size: size ?? this.size,\n      sort: sort ?? this.sort,\n    );\n  }\n\n  @override\n  List<Object?> get props => [page, sort];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/data/models/payee_query_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'payee_query_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nPayeeQueryRequest _$PayeeQueryRequestFromJson(Map<String, dynamic> json) =>\n    PayeeQueryRequest(\n      page: json['page'] as int? ?? 1,\n      size: json['size'] as int? ?? 15,\n      sort: json['sort'] as String? ?? 'id,desc',\n    );\n\nMap<String, dynamic> _$PayeeQueryRequestToJson(PayeeQueryRequest instance) =>\n    <String, dynamic>{\n      'page': instance.page,\n      'size': instance.size,\n      'sort': instance.sort,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/data/payee_repository.dart",
    "content": "import 'dart:convert';\n\nimport '/payees/payees.dart';\nimport '/commons/commons.dart';\n\nclass PayeeRepository {\n\n  List<Payee> _responseToList(String response) {\n    return (json.decode(response)['data']).map<Payee>((i) => Payee.fromJson(i)).toList();\n  }\n\n  Payee _responsePayee(String response) {\n    return Payee.fromJson(json.decode(response)['data']);\n  }\n\n  Future<List<Payee>> getExpenseable() async {\n    String response = await HttpClient().get('payees/expenseable');\n    return _responseToList(response);\n  }\n\n  Future<List<Payee>> getIncomeable() async {\n    String response = await HttpClient().get('payees/incomeable');\n    return _responseToList(response);\n  }\n\n  Future<List<Payee>> getEnable() async {\n    String response = await HttpClient().get('payees/enable');\n    return _responseToList(response);\n  }\n\n  Future<List<Payee>> query(PayeeQueryRequest request) async {\n    String response = await HttpClient().get('payees', params: request.toJson());\n    return (json.decode(response)['data']['content']).map<Payee>((i) => Payee.fromJson(i)).toList();\n  }\n\n  Future<Payee> get(int id) async {\n    return _responsePayee(await HttpClient().get('payees/$id'));\n  }\n\n  Future<bool> delete(String id) async {\n    String response = await HttpClient().delete('payees/$id');\n    return parseResponse(response);\n  }\n\n  Future<bool> toggle(String id) async {\n    String response = await HttpClient().put('payees/$id/toggle');\n    return parseResponse(response);\n  }\n\n  Future<bool> add(PayeeFormRequest request) async {\n    String response = await HttpClient().post('payees', data: request.toJson());\n    return parseResponse(response);\n  }\n\n  Future<bool> update(int id, PayeeFormRequest request) async {\n    String response = await HttpClient().put('payees/$id', data: request.toJson());\n    return parseResponse(response);\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/payees.dart",
    "content": "export 'bloc/payee_enable/payee_enable_bloc.dart';\nexport 'bloc/payee_expenseable/payee_expenseable_bloc.dart';\nexport 'bloc/payee_incomeable/payee_incomeable_bloc.dart';\nexport 'bloc/payees/payees_bloc.dart';\nexport 'bloc/payee_fetch/payee_fetch_bloc.dart';\nexport 'bloc/payee_form/payee_form_bloc.dart';\nexport 'data/payee_repository.dart';\nexport 'data/models/payee.dart';\nexport 'data/models/payee_query_request.dart';\nexport 'data/models/payee_form_request.dart';\nexport 'ui/payees_page.dart';\nexport 'ui/payee_form_page.dart';\nexport 'ui/payee_detail_page.dart';\nexport 'ui/payee_form/name_input.dart';\nexport 'ui/payee_form/expenseable_input.dart';\nexport 'ui/payee_form/incomeable_input.dart';\nexport 'ui/payee_form/notes_input.dart';\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/ui/payee_detail_page.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/payees/payees.dart';\n\nclass PayeeDetailPage extends StatefulWidget {\n\n  final Payee payee;\n  PayeeDetailPage({\n    required this.payee\n  });\n\n  @override\n  State<PayeeDetailPage> createState() => _PayeeDetailPageState();\n}\n\n\nclass _PayeeDetailPageState extends State<PayeeDetailPage> {\n\n  @override\n  void initState() {\n    BlocProvider.of<PayeeFetchBloc>(context).add(PayeeLoadDefault(payee: widget.payee));\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<PayeesBloc, PayeesState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              // Navigator.pop(context);\n              if (Navigator.canPop(context)) {\n                Navigator.of(context).pop();\n              } else {\n                SystemNavigator.pop();\n              }\n            }\n          }\n        ),\n        BlocListener<PayeesBloc, PayeesState>(\n          listenWhen: (previous, current) => previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              BlocProvider.of<PayeeFetchBloc>(context).add(PayeeFetched());\n            }\n          },\n        )\n      ],\n      child: BlocBuilder<PayeeFetchBloc, PayeeFetchState>(\n        builder: (context, state) {\n          return Scaffold(\n              appBar: AppBar(\n                centerTitle: true,\n                title: const Text('交易对象详情'),\n                actions: _buildActions(context, state.payee ?? widget.payee)\n              ),\n              body: Builder(\n                builder: (context) {\n                  switch (state.status) {\n                    case LoadDataStatus.progress:\n                    case LoadDataStatus.initial:\n                      return const PageLoading();\n                    case LoadDataStatus.success:\n                      return _buildBody(context, state.payee ?? widget.payee);\n                    default:\n                      return PageError(onTap: () { BlocProvider.of<PayeeFetchBloc>(context).add(PayeeFetched()); });\n                  }\n                },\n              )\n          );\n        }\n        )\n    );\n  }\n\n  List<Widget> _buildActions(BuildContext context, Payee payee) {\n    return [\n      IconButton(\n        icon: Icon(Icons.edit),\n        onPressed: () {\n          fullDialog(context, PayeeFormPage(type: 2, payee: payee));\n        }\n      ),\n      IconButton(\n        icon: Icon(Icons.delete),\n        onPressed: () async {\n          if (await confirm(\n            context,\n            content: Text(\"确定删除${payee.name}吗？\"),\n            textOK: Text(\"确定\"),\n            textCancel: Text(\"取消\"),\n          )) {\n            BlocProvider.of<PayeesBloc>(context).add(PayeeDeleted(payee.id.toString()));\n          }\n        }\n      )\n    ];\n  }\n\n  Widget _buildBody(BuildContext context, Payee payee) {\n    final theme = Theme.of(context);\n    TextStyle? style1 = theme.textTheme.bodyText2;\n    TextStyle? style2 = theme.textTheme.bodyText1;\n    return SingleChildScrollView(\n      child: Padding(\n        padding: EdgeInsets.symmetric(horizontal: 15),\n        child: Column(\n          children: [\n            SizedBox(height: 20),\n            Row(children: [Text(\"名称：\", style: style1), Text(payee.name, style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可支出：\", style: style1), Text(boolToString(payee.expenseable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可收入：\", style: style1), Text(boolToString(payee.incomeable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"备注：\", style: style1), Flexible(child: Text(payee.notes ?? '', style: style2))]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可用：\", style: style1), Text(boolToString(payee.enable), style: style2)]),\n            SizedBox(height: 20),\n            SizedBox(\n              width: double.infinity,\n              child: ElevatedButton(\n                child: Text(payee.enable ? '禁用' : '启用'),\n                onPressed: () {\n                  BlocProvider.of<PayeesBloc>(context).add(PayeeToggled(payee.id.toString()));\n                }\n              ),\n            )\n          ],\n        )\n      ),\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/ui/payee_form/expenseable_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/payees/payees.dart';\nimport '/commons/commons.dart';\n\nclass ExpenseableSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可支出\",\n        BlocSelector<PayeeFormBloc, PayeeFormState, bool?>(\n          selector: (state) => state.request.expenseable,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<PayeeFormBloc>().add(PayeeFormExpenseableChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/ui/payee_form/incomeable_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/payees/payees.dart';\nimport '/commons/commons.dart';\n\nclass IncomeableSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可支出\",\n        BlocSelector<PayeeFormBloc, PayeeFormState, bool?>(\n          selector: (state) => state.request.incomeable,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<PayeeFormBloc>().add(PayeeFormIncomeableChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/ui/payee_form/name_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/payees/payees.dart';\n\nclass NameInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n          '名称',\n          BlocBuilder<PayeeFormBloc, PayeeFormState>(\n            buildWhen: (previous, current) => previous.request.name != current.request.name,\n            builder: (context, state) {\n              controller.value = TextEditingValue(\n                text: state.request.name ?? '',\n                selection: TextSelection.fromPosition(\n                  TextPosition(offset: state.request.name?.length ?? 0),\n                ),\n              );\n              return\n                TextField(\n                  controller: controller,\n                  onChanged: (value) => context.read<PayeeFormBloc>().add(PayeeFormNameChanged(value)),\n                  decoration: InputDecoration(\n                    contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                  ),\n                );\n            }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/ui/payee_form/notes_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/payees/payees.dart';\nimport '/commons/commons.dart';\n\nclass NotesInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n          '备注',\n          BlocBuilder<PayeeFormBloc, PayeeFormState>(\n              buildWhen: (previous, current) => previous.request.notes != current.request.notes,\n              builder: (context, state) {\n                controller.value = TextEditingValue(\n                  text: state.request.notes ?? '',\n                  selection: TextSelection.fromPosition(\n                    TextPosition(offset: state.request.notes?.length ?? 0),\n                  ),\n                );\n                return\n                  TextField(\n                    controller: controller,\n                    onChanged: (value) => context.read<PayeeFormBloc>().add(PayeeFormNotesChanged(value)),\n                    decoration: InputDecoration(),\n                  );\n              }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/ui/payee_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\n\nimport '/payees/payees.dart';\nimport '/commons/commons.dart';\n\nclass PayeeFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final Payee? payee;\n\n  const PayeeFormPage({\n    required this.type,\n    this.payee,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => PayeeFormBloc(payeeRepository: RepositoryProvider.of<PayeeRepository>(context))..add(PayeeFormDefaultLoaded(type, payee)),\n      child: BlocListener<PayeeFormBloc, PayeeFormState>(\n        listener: (context, state) {\n          if (state.status == FormzStatus.submissionSuccess) {\n            Message.success('操作成功');\n            Navigator.of(context).pop();\n            BlocProvider.of<PayeesBloc>(context).add(PayeesRefreshed());\n            if (type == 2) {\n              BlocProvider.of<PayeeFetchBloc>(context).add(PayeeFetched());\n            }\n          }\n        },\n        child: Builder(\n          builder: (context) {\n            return Scaffold(\n              appBar: AppBar(\n                  centerTitle: true,\n                  title: Text(_buildTitle(type)),\n                  actions: [\n                    IconButton(\n                      icon: Icon(Icons.done),\n                      onPressed: () {\n                        BlocProvider.of<PayeeFormBloc>(context).add(PayeeFormSubmitted(type, payee));\n                      }\n                    )\n                  ]\n              ),\n              body: SingleChildScrollView(\n                child: Padding(\n                  padding: EdgeInsets.symmetric(horizontal: 15),\n                  child: Column(\n                    children: [\n                      NameInput(),\n                      SizedBox(height: 10),\n                      ExpenseableSwitch(),\n                      IncomeableSwitch(),\n                      NotesInput()\n                    ],\n                  )\n                )\n              ),\n            );\n          }\n        )\n\n      )\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增交易对象';\n      case 2:\n        return '修改交易对象';\n      default:\n        return '账户操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/payees/ui/payees_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:pull_to_refresh/pull_to_refresh.dart';\nimport '/routes.dart';\nimport '/commons/commons.dart';\nimport '/payees/payees.dart';\nimport '/components/components.dart';\n\nclass PayeesPage extends StatefulWidget {\n  @override\n  State<PayeesPage> createState() => _PayeesPageState();\n}\n\nclass _PayeesPageState extends State<PayeesPage> {\n\n  RefreshController _refreshController = RefreshController(initialRefresh: false);\n\n  @override\n  void initState() {\n    BlocProvider.of<PayeesBloc>(context).add(PayeesRefreshed());\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<PayeesBloc, PayeesState>(\n          listenWhen: (previous, current) => previous.loadMoreStatus != current.loadMoreStatus,\n          listener: (context, state) {\n            if (state.loadMoreStatus == LoadDataStatus.success) {\n              _refreshController.loadComplete();\n            } else if (state.loadMoreStatus == LoadDataStatus.failure) {\n              _refreshController.loadFailed();\n            } else if (state.loadMoreStatus == LoadDataStatus.empty) {\n              _refreshController.loadNoData();\n            }\n          },\n        ),\n        BlocListener<PayeesBloc, PayeesState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              BlocProvider.of<PayeesBloc>(context).add(PayeesRefreshed());\n            }\n          },\n        ),\n        BlocListener<PayeesBloc, PayeesState>(\n          listenWhen: (previous, current) => previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              BlocProvider.of<PayeesBloc>(context).add(PayeesRefreshed());\n            }\n          },\n        ),\n      ],\n      child: Scaffold(\n        appBar: AppBar(\n          title: const Text(\"交易对象\"),\n          centerTitle: true,\n          actions: [\n            IconButton(\n                icon: const Icon(Icons.add),\n                onPressed: () {\n                  fullDialog(context, PayeeFormPage(type: 1));\n                }\n            )\n          ]\n        ),\n        body: BlocBuilder<PayeesBloc, PayeesState>(\n          buildWhen: (previous, current) => previous.status != current.status || previous.payees != current.payees,\n          builder: (context, state) {\n            switch (state.status) {\n              case LoadDataStatus.progress:\n              case LoadDataStatus.initial:\n                return const PageLoading();\n              case LoadDataStatus.success:\n                if (state.payees.isEmpty) return Empty();\n                return SmartRefresher(\n                  enablePullDown: true,\n                  enablePullUp: true,\n                  controller: _refreshController,\n                  child: _buildList(context, state.payees),\n                  onRefresh: () async {\n                    BlocProvider.of<PayeesBloc>(context).add(PayeesRefreshed());\n                    _refreshController.refreshCompleted();\n                  },\n                  onLoading: () async {\n                    BlocProvider.of<PayeesBloc>(context).add(PayeesLoadMore());\n                  },\n                );\n              default:\n                return PageError(onTap: () { BlocProvider.of<PayeesBloc>(context).add(PayeesRefreshed()); });\n            }\n          }\n        ),\n      )\n    );\n  }\n\n  Widget _buildList(BuildContext context, List<Payee> payees) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      itemCount: payees.length,\n      itemBuilder: (context, index) {\n        Payee payee = payees[index];\n        return ListTile(\n          dense: true,\n          title: Text(payee.name, style: theme.textTheme.bodyText1,),\n          subtitle: payee.notes != null && payee.notes!.isNotEmpty ? Text(payee.notes!, style: theme.textTheme.caption) : null,\n          trailing: Icon(Icons.keyboard_arrow_right),\n          onTap: () {\n            Navigator.pushNamed(context, '/payee-detail', arguments: PayeeDetailArguments(payee: payee));\n          },\n        );\n      },\n      separatorBuilder: (context, index) {\n        return Divider();\n      },\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/routes.dart",
    "content": "import 'package:bookkeeping_user_flutter/charts/charts.dart';\nimport 'package:bookkeeping_user_flutter/items/items.dart';\nimport 'package:flutter/material.dart';\nimport '/components/components.dart';\nimport 'start_page.dart';\nimport 'index.dart';\nimport '/accounts/accounts.dart';\nimport '/categories/categories.dart';\nimport '/flows/flows.dart';\nimport '/payees/payees.dart';\nimport '/books/books.dart';\nimport '/tags/tags.dart';\n\nclass AppRouter {\n\n  static final Map<String, WidgetBuilder> routes = {\n    '/': (context) => StartPage(),\n    '/index': (context) => IndexPage(),\n    '/flows-filter': (context) => FlowsFilterPage(),\n    '/expense-categories': (context) => ExpenseCategoryPage(),\n    '/income-categories': (context) => IncomeCategoryPage(),\n    '/payees': (context) => PayeesPage(),\n    '/books': (context) => BooksPage(),\n    '/tags': (context) => TagsPage(),\n    '/charts-expense-category-filter': (context) => ChartsExpenseCategoryFilterPage(),\n    '/charts-income-category-filter': (context) => ChartsIncomeCategoryFilterPage(),\n    '/items-index': (context) => ItemsIndexPage()\n  };\n\n  static final String initialRoute = '/';\n\n  static final RouteFactory generateRoute = (settings) {\n    if (settings.name == '/flow-detail') {\n      final args = settings.arguments as FlowDetailArguments;\n      return MaterialPageRoute(\n          builder: (_) => FlowDetailPage(flow: args.flow)\n      );\n    }\n    if (settings.name == '/account-detail') {\n      final args = settings.arguments as AccountDetailArguments;\n      return MaterialPageRoute(\n          builder: (_) => AccountDetailPage(account: args.account)\n      );\n    }\n    if (settings.name == '/payee-detail') {\n      final args = settings.arguments as PayeeDetailArguments;\n      return MaterialPageRoute(\n          builder: (_) => PayeeDetailPage(payee: args.payee)\n      );\n    }\n    if (settings.name == '/book-detail') {\n      final args = settings.arguments as BookDetailArguments;\n      return MaterialPageRoute(\n          builder: (_) => BookDetailPage(book: args.book)\n      );\n    }\n    if (settings.name == '/category-detail') {\n      final args = settings.arguments as CategoryDetailArguments;\n      return MaterialPageRoute(\n          builder: (_) => CategoryDetailPage(category: args.category, categoryType: args.categoryType)\n      );\n    }\n    if (settings.name == '/tag-detail') {\n      final args = settings.arguments as TagDetailArguments;\n      return MaterialPageRoute(\n          builder: (_) => TagDetailPage(tag: args.tag)\n      );\n    }\n    return null;\n  };\n\n  static final RouteFactory unknownRoute = (settings) {\n    return MaterialPageRoute(\n        builder: (_) => const PageError(msg: '路由找不到')\n    );\n  };\n\n}\n\nclass FlowDetailArguments {\n  final FlowModel flow;\n  FlowDetailArguments({\n    required this.flow,\n  });\n}\n\nclass AccountDetailArguments {\n  final Account account;\n  AccountDetailArguments({\n    required this.account,\n  });\n}\n\nclass PayeeDetailArguments {\n  final Payee payee;\n  PayeeDetailArguments({\n    required this.payee,\n  });\n}\n\nclass BookDetailArguments {\n  final Book book;\n  BookDetailArguments({\n    required this.book,\n  });\n}\n\nclass CategoryDetailArguments {\n  final int categoryType;\n  final Category category;\n  CategoryDetailArguments({\n    required this.category,\n    required this.categoryType\n  });\n}\n\nclass TagDetailArguments {\n  final Tag tag;\n  TagDetailArguments({\n    required this.tag,\n  });\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/start_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/components/components.dart';\nimport '/login/login.dart';\nimport 'index.dart';\n\nclass StartPage extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return BlocBuilder<AuthBloc, AuthState>(\n      builder: (BuildContext context, AuthState state) {\n        if (state.status == AuthStatus.uninitialized) {\n          return PageLoading();\n        }\n        if (state.status == AuthStatus.authenticated) {\n          return IndexPage();\n        }\n        if (state.status == AuthStatus.unauthenticated) {\n          return LoginPage();\n        }\n        if (state.status == AuthStatus.loading) {\n          return PageLoading();\n        }\n        return Text('error');\n      }\n    );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_enable/tag_enable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/tags/tags.dart';\n\npart 'tag_enable_event.dart';\npart 'tag_enable_state.dart';\n\nclass TagEnableBloc extends Bloc<TagEnableEvent, TagEnableState> {\n\n  final TagRepository tagRepository;\n\n  TagEnableBloc({\n    required this.tagRepository\n  }) : super(TagEnableStateLoadInProgress()) {\n    on<TagEnableLoaded>(_onTagEnableLoaded);\n  }\n\n  void _onTagEnableLoaded(_, Emitter<TagEnableState> emit) async {\n    // 只加载一次数据\n    // if (state is TagEnableStateLoadSuccess) return;\n    try {\n      emit(TagEnableStateLoadInProgress());\n      final tags = await tagRepository.getEnable();\n      emit(TagEnableStateLoadSuccess(tags));\n    } catch (_) {\n      emit(TagEnableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_enable/tag_enable_event.dart",
    "content": "part of 'tag_enable_bloc.dart';\n\nabstract class TagEnableEvent extends Equatable {\n\n  const TagEnableEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass TagEnableLoaded extends TagEnableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_enable/tag_enable_state.dart",
    "content": "part of 'tag_enable_bloc.dart';\n\nabstract class TagEnableState extends Equatable {\n\n  const TagEnableState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass TagEnableStateLoadInProgress extends TagEnableState { }\n\nclass TagEnableStateLoadSuccess extends TagEnableState {\n\n  final List<Tag> tags;\n\n  const TagEnableStateLoadSuccess(this.tags);\n\n  @override\n  List<Object> get props => [tags];\n\n}\n\nclass TagEnableStateLoadFailure extends TagEnableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_expenseable/tag_expenseable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/tags/tags.dart';\n\npart 'tag_expenseable_event.dart';\npart 'tag_expenseable_state.dart';\n\nclass TagExpenseableBloc extends Bloc<TagExpenseableEvent, TagExpenseableState> {\n\n  final TagRepository tagRepository;\n\n  TagExpenseableBloc({\n    required this.tagRepository\n  }) : super(TagExpenseableStateLoadInProgress()) {\n    on<TagExpenseableLoaded>(_onTagExpenseableLoaded);\n  }\n\n  void _onTagExpenseableLoaded(_, Emitter<TagExpenseableState> emit) async {\n    // 只加载一次数据\n    // if (state is TagExpenseableStateLoadSuccess) return;\n    try {\n      emit(TagExpenseableStateLoadInProgress());\n      final tags = await tagRepository.getExpenseable();\n      emit(TagExpenseableStateLoadSuccess(tags));\n    } catch (_) {\n      emit(TagExpenseableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_expenseable/tag_expenseable_event.dart",
    "content": "part of 'tag_expenseable_bloc.dart';\n\nabstract class TagExpenseableEvent extends Equatable {\n\n  const TagExpenseableEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass TagExpenseableLoaded extends TagExpenseableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_expenseable/tag_expenseable_state.dart",
    "content": "part of 'tag_expenseable_bloc.dart';\n\nabstract class TagExpenseableState extends Equatable {\n\n  const TagExpenseableState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass TagExpenseableStateLoadInProgress extends TagExpenseableState { }\n\nclass TagExpenseableStateLoadSuccess extends TagExpenseableState {\n\n  final List<Tag> tags;\n\n  const TagExpenseableStateLoadSuccess(this.tags);\n\n  @override\n  List<Object> get props => [tags];\n\n}\n\nclass TagExpenseableStateLoadFailure extends TagExpenseableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_fetch/tag_fetch_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:bloc/bloc.dart';\nimport 'package:meta/meta.dart';\nimport '/tags/tags.dart';\nimport '/commons/commons.dart';\n\npart 'tag_fetch_event.dart';\npart 'tag_fetch_state.dart';\n\nclass TagFetchBloc extends Bloc<TagFetchEvent, TagFetchState> {\n\n  final TagRepository tagRepository;\n\n  TagFetchBloc({\n    required this.tagRepository,\n  }) : super(TagFetchState()) {\n    on<TagFetched>(_onFetched);\n    on<TagLoadDefault>(_onDefault);\n  }\n\n  void _onDefault(TagLoadDefault event, Emitter<TagFetchState> emit) {\n    emit(state.copyWith(\n      status: LoadDataStatus.success,\n      tag: event.tag\n    ));\n  }\n\n  void _onFetched(_, Emitter<TagFetchState> emit) async {\n    try {\n      emit(state.copyWith(status: LoadDataStatus.progress));\n      final tag = await tagRepository.get(state.tag!.id);\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        tag: tag,\n      ));\n    } catch (_) {\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_fetch/tag_fetch_event.dart",
    "content": "part of 'tag_fetch_bloc.dart';\n\n@immutable\nclass TagFetchEvent extends Equatable {\n  const TagFetchEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass TagFetched extends TagFetchEvent {}\n\nclass TagLoadDefault extends TagFetchEvent {\n  final Tag tag;\n  const TagLoadDefault({\n    required this.tag,\n  });\n  List<Object> get props => [tag];\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_fetch/tag_fetch_state.dart",
    "content": "part of 'tag_fetch_bloc.dart';\n\n@immutable\nclass TagFetchState extends Equatable {\n\n  final LoadDataStatus status;\n  final Tag? tag;\n\n  const TagFetchState({\n    this.status = LoadDataStatus.initial,\n    this.tag,\n  });\n\n  TagFetchState copyWith({\n    LoadDataStatus? status,\n    Tag? tag,\n  }) {\n    return TagFetchState(\n      status: status ?? this.status,\n      tag: tag ?? this.tag,\n    );\n  }\n\n  @override\n  List<Object?> get props => [status, tag];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_form/tag_form_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:formz/formz.dart';\nimport 'package:meta/meta.dart';\nimport '/tags/tags.dart';\n\npart 'tag_form_event.dart';\npart 'tag_form_state.dart';\n\nclass TagFormBloc extends Bloc<TagFormEvent, TagFormState> {\n\n  final TagRepository tagRepository;\n  final TagExpenseableBloc tagExpenseableBloc;\n  final TagIncomeableBloc tagIncomeableBloc;\n  final TagTransferableBloc tagTransferableBloc;\n  final TagEnableBloc tagEnableBloc;\n\n  TagFormBloc({\n    required this.tagRepository,\n    required this.tagExpenseableBloc,\n    required this.tagIncomeableBloc,\n    required this.tagTransferableBloc,\n    required this.tagEnableBloc\n  }) : super(const TagFormState()) {\n    on<TagFormNameChanged>(_onNameChanged);\n    on<TagFormNotesChanged>(_onNotesChanged);\n    on<TagFormParentChanged>(_onParentChanged);\n    on<TagFormExpenseableChanged>(_onExpenseableChanged);\n    on<TagFormIncomeableChanged>(_onIncomeableChanged);\n    on<TagFormTransferableChanged>(_onTransferableChanged);\n    on<TagFormDefaultLoaded>(_onDefaultLoaded);\n    on<TagFormSubmitted>(_onSubmitted);\n  }\n\n  void _onNameChanged(TagFormNameChanged event, Emitter<TagFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(name: event.name),\n    ));\n  }\n\n  void _onNotesChanged(TagFormNotesChanged event, Emitter<TagFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(notes: event.notes),\n    ));\n  }\n\n  void _onParentChanged(TagFormParentChanged event, Emitter<TagFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(parentId: event.parentId),\n    ));\n  }\n\n  void _onExpenseableChanged(TagFormExpenseableChanged event, Emitter<TagFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(expenseable: event.expenseable),\n    ));\n  }\n\n  void _onIncomeableChanged(TagFormIncomeableChanged event, Emitter<TagFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(incomeable: event.incomeable),\n    ));\n  }\n\n  void _onTransferableChanged(TagFormTransferableChanged event, Emitter<TagFormState> emit) {\n    emit(state.copyWith(\n      request: state.request.copyWith(transferable: event.transferable),\n    ));\n  }\n\n  void _onDefaultLoaded(TagFormDefaultLoaded event, Emitter<TagFormState> emit) {\n    emit(state.copyWith(\n      status: FormzStatus.pure,\n      request: state.request.copyWith(\n        name: event.type == 2 ? event.tag?.name ?? '' : '',\n        notes: event.type == 2 ? event.tag?.notes ?? '' : '',\n        parentId: event.type == 2 ? event.tag?.parentId.toString() : event.tag?.id.toString(),\n        expenseable: event.tag?.expenseable ?? true,\n        incomeable: event.tag?.incomeable ?? false,\n        transferable: event.tag?.transferable ?? false\n      )\n    ));\n  }\n\n  void _onSubmitted(TagFormSubmitted event, Emitter<TagFormState> emit) async {\n    try {\n      bool result = false;\n      switch (event.type) {\n        case 1:\n          result = await tagRepository.add(state.request);\n          break;\n        case 2:\n          result = await tagRepository.update(event.tag!.id, state.request);\n          break;\n        default:\n          break;\n      }\n      if (result) {\n        emit(state.copyWith(status: FormzStatus.submissionSuccess));\n        tagExpenseableBloc.add(TagExpenseableLoaded());\n        tagIncomeableBloc.add(TagIncomeableLoaded());\n        tagTransferableBloc.add(TagTransferableLoaded());\n        tagEnableBloc.add(TagEnableLoaded());\n      } else {\n        emit(state.copyWith(status: FormzStatus.submissionFailure));\n      }\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: FormzStatus.submissionFailure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_form/tag_form_event.dart",
    "content": "part of 'tag_form_bloc.dart';\n\n@immutable\nabstract class TagFormEvent extends Equatable {\n  const TagFormEvent();\n  @override\n  List<Object?> get props => [];\n}\n\nclass TagFormNameChanged extends TagFormEvent {\n  const TagFormNameChanged(this.name);\n  final String name;\n  @override\n  List<Object> get props => [name];\n}\n\nclass TagFormNotesChanged extends TagFormEvent {\n  const TagFormNotesChanged(this.notes);\n  final String notes;\n  @override\n  List<Object> get props => [notes];\n}\n\nclass TagFormParentChanged extends TagFormEvent {\n  const TagFormParentChanged(this.parentId);\n  final String parentId;\n  @override\n  List<Object> get props => [parentId];\n}\n\nclass TagFormExpenseableChanged extends TagFormEvent {\n  const TagFormExpenseableChanged(this.expenseable);\n  final bool expenseable;\n  @override\n  List<Object> get props => [expenseable];\n}\n\nclass TagFormIncomeableChanged extends TagFormEvent {\n  const TagFormIncomeableChanged(this.incomeable);\n  final bool incomeable;\n  @override\n  List<Object> get props => [incomeable];\n}\n\nclass TagFormTransferableChanged extends TagFormEvent {\n  const TagFormTransferableChanged(this.transferable);\n  final bool transferable;\n  @override\n  List<Object> get props => [transferable];\n}\n\nclass TagFormDefaultLoaded extends TagFormEvent {\n  final int type;\n  final Tag? tag;\n  const TagFormDefaultLoaded(this.type, this.tag);\n  @override\n  List<Object?> get props => [type, tag];\n}\n\nclass TagFormSubmitted extends TagFormEvent {\n  final int type;\n  final Tag? tag;\n  const TagFormSubmitted(this.type, this.tag);\n  @override\n  List<Object?> get props => [type, tag];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_form/tag_form_state.dart",
    "content": "part of 'tag_form_bloc.dart';\n\n@immutable\nclass TagFormState extends Equatable {\n\n  final FormzStatus status;\n  final TagFormRequest request;\n\n  const TagFormState({\n    this.status = FormzStatus.pure,\n    this.request = const TagFormRequest(),\n  });\n\n  TagFormState copyWith({\n    FormzStatus? status,\n    TagFormRequest? request,\n  }) {\n    return TagFormState(\n      status: status ?? this.status,\n      request: request ?? this.request,\n    );\n  }\n\n  @override\n  List<Object> get props => [status, request];\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_incomeable/tag_incomeable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/tags/tags.dart';\n\npart 'tag_incomeable_event.dart';\npart 'tag_incomeable_state.dart';\n\nclass TagIncomeableBloc extends Bloc<TagIncomeableEvent, TagIncomeableState> {\n\n  final TagRepository tagRepository;\n\n  TagIncomeableBloc({\n    required this.tagRepository\n  }) : super(TagIncomeableStateLoadInProgress()) {\n    on<TagIncomeableLoaded>(_onTagIncomeableLoaded);\n  }\n\n  void _onTagIncomeableLoaded(_, Emitter<TagIncomeableState> emit) async {\n    // 只加载一次数据\n    // if (state is TagIncomeableStateLoadSuccess) return;\n    try {\n      emit(TagIncomeableStateLoadInProgress());\n      final tags = await tagRepository.getIncomeable();\n      emit(TagIncomeableStateLoadSuccess(tags));\n    } catch (_) {\n      emit(TagIncomeableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_incomeable/tag_incomeable_event.dart",
    "content": "part of 'tag_incomeable_bloc.dart';\n\nabstract class TagIncomeableEvent extends Equatable {\n\n  const TagIncomeableEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass TagIncomeableLoaded extends TagIncomeableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_incomeable/tag_incomeable_state.dart",
    "content": "part of 'tag_incomeable_bloc.dart';\n\nabstract class TagIncomeableState extends Equatable {\n\n  const TagIncomeableState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass TagIncomeableStateLoadInProgress extends TagIncomeableState { }\n\nclass TagIncomeableStateLoadSuccess extends TagIncomeableState {\n\n  final List<Tag> tags;\n\n  const TagIncomeableStateLoadSuccess(this.tags);\n\n  @override\n  List<Object> get props => [tags];\n\n}\n\nclass TagIncomeableStateLoadFailure extends TagIncomeableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_transferable/tag_transferable_bloc.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/tags/tags.dart';\n\npart 'tag_transferable_event.dart';\npart 'tag_transferable_state.dart';\n\nclass TagTransferableBloc extends Bloc<TagTransferableEvent, TagTransferableState> {\n\n  final TagRepository tagRepository;\n\n  TagTransferableBloc({\n    required this.tagRepository\n  }) : super(TagTransferableStateLoadInProgress()) {\n    on<TagTransferableLoaded>(_onTagTransferableLoaded);\n  }\n\n  void _onTagTransferableLoaded(_, Emitter<TagTransferableState> emit) async {\n    // 只加载一次数据\n    // if (state is TagTransferableStateLoadSuccess) return;\n    try {\n      emit(TagTransferableStateLoadInProgress());\n      final tags = await tagRepository.getTransferable();\n      emit(TagTransferableStateLoadSuccess(tags));\n    } catch (_) {\n      emit(TagTransferableStateLoadFailure());\n    }\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_transferable/tag_transferable_event.dart",
    "content": "part of 'tag_transferable_bloc.dart';\n\nabstract class TagTransferableEvent extends Equatable {\n\n  const TagTransferableEvent();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass TagTransferableLoaded extends TagTransferableEvent { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_transferable/tag_transferable_state.dart",
    "content": "part of 'tag_transferable_bloc.dart';\n\nabstract class TagTransferableState extends Equatable {\n\n  const TagTransferableState();\n\n  @override\n  List<Object> get props => [];\n\n}\n\nclass TagTransferableStateLoadInProgress extends TagTransferableState { }\n\nclass TagTransferableStateLoadSuccess extends TagTransferableState {\n\n  final List<Tag> tags;\n\n  const TagTransferableStateLoadSuccess(this.tags);\n\n  @override\n  List<Object> get props => [tags];\n\n}\n\nclass TagTransferableStateLoadFailure extends TagTransferableState { }"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_tree/tag_tree_bloc.dart",
    "content": "import 'package:bloc/bloc.dart';\nimport 'package:equatable/equatable.dart';\nimport 'package:meta/meta.dart';\nimport '/tags/tags.dart';\nimport '/commons/commons.dart';\n\npart 'tag_tree_event.dart';\npart 'tag_tree_state.dart';\n\nclass TagTreeBloc extends Bloc<TagTreeEvent, TagTreeState> {\n\n  final TagRepository tagRepository;\n  final TagExpenseableBloc tagExpenseableBloc;\n  final TagIncomeableBloc tagIncomeableBloc;\n  final TagTransferableBloc tagTransferableBloc;\n  final TagEnableBloc tagEnableBloc;\n\n  TagTreeBloc({\n    required this.tagRepository,\n    required this.tagExpenseableBloc,\n    required this.tagIncomeableBloc,\n    required this.tagTransferableBloc,\n    required this.tagEnableBloc,\n  }) : super(TagTreeState()) {\n    on<TagTreeRefreshed>(_onRefreshed);\n    on<TagItemClicked>(_onItemClicked);\n    on<TagBackClicked>(_onBackClicked);\n    on<TagToggled>(_onToggled);\n    on<TagDeleted>(_onDeleted);\n  }\n\n  void _onRefreshed(_, Emitter<TagTreeState> emit) async {\n    try {\n      emit(state.copyWith(\n        status: LoadDataStatus.progress,\n      ));\n      final tags = await tagRepository.query();\n      emit(state.copyWith(\n        status: LoadDataStatus.success,\n        tags: tags,\n        currentTags: tags\n      ));\n    } catch (_) {\n      print(_);\n      emit(state.copyWith(status: LoadDataStatus.failure));\n    }\n  }\n\n  void _onItemClicked(TagItemClicked event, Emitter<TagTreeState> emit) async {\n    List<List<TagTree>> newHistory = List.from(state.history);\n    newHistory.add(state.currentTags);\n    emit(state.copyWith(\n      currentLevel: state.currentLevel + 1,\n      currentTags: event.tagTree.children,\n      history: newHistory,\n    ));\n  }\n\n  void _onBackClicked(TagBackClicked event, Emitter<TagTreeState> emit) async {\n    emit(state.copyWith(\n      currentLevel: state.currentLevel - 1,\n      currentTags: state.history.last\n    ));\n  }\n\n  void _onToggled(TagToggled event, Emitter<TagTreeState> emit) async {\n    try {\n      emit(state.copyWith(toggleStatus: LoadDataStatus.progress));\n      final result = await tagRepository.toggle(event.id);\n      if (result) {\n        emit(state.copyWith(toggleStatus: LoadDataStatus.success));\n        tagExpenseableBloc.add(TagExpenseableLoaded());\n        tagIncomeableBloc.add(TagIncomeableLoaded());\n        tagTransferableBloc.add(TagTransferableLoaded());\n        tagEnableBloc.add(TagEnableLoaded());\n      } else {\n        emit(state.copyWith(toggleStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(toggleStatus: LoadDataStatus.failure));\n    }\n  }\n\n  void _onDeleted(TagDeleted event, Emitter<TagTreeState> emit) async {\n    try {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.progress));\n      final result = await tagRepository.delete(event.id);\n      if (result) {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.success));\n        tagExpenseableBloc.add(TagExpenseableLoaded());\n        tagIncomeableBloc.add(TagIncomeableLoaded());\n        tagTransferableBloc.add(TagTransferableLoaded());\n        tagEnableBloc.add(TagEnableLoaded());\n      } else {\n        emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n      }\n    } catch (_) {\n      emit(state.copyWith(deleteStatus: LoadDataStatus.failure));\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_tree/tag_tree_event.dart",
    "content": "part of 'tag_tree_bloc.dart';\n\n@immutable\nabstract class TagTreeEvent extends Equatable {\n  const TagTreeEvent();\n  @override\n  List<Object> get props => [];\n}\n\nclass TagTreeRefreshed extends TagTreeEvent { }\n\nclass TagItemClicked extends TagTreeEvent {\n  final TagTree tagTree;\n  const TagItemClicked({\n    required this.tagTree,\n  });\n}\n\nclass TagBackClicked extends TagTreeEvent { }\n\nclass TagDeleted extends TagTreeEvent {\n  final String id;\n  const TagDeleted(this.id);\n  @override\n  List<Object> get props => [id];\n}\n\nclass TagToggled extends TagTreeEvent {\n  final String id;\n  const TagToggled(this.id);\n  @override\n  List<Object> get props => [id];\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/bloc/tag_tree/tag_tree_state.dart",
    "content": "part of 'tag_tree_bloc.dart';\n\n@immutable\nclass TagTreeState extends Equatable {\n\n  final LoadDataStatus status;\n  final List<TagTree> tags;\n  final LoadDataStatus deleteStatus;\n  final LoadDataStatus toggleStatus;\n  final List<TagTree> currentTags;\n  final int currentLevel;\n  final List<List<TagTree>> history;\n\n  @override\n  List<Object?> get props => [status, tags, deleteStatus, toggleStatus, currentTags, currentLevel, history];\n\n  const TagTreeState({\n    this.status = LoadDataStatus.initial,\n    this.tags = const [],\n    this.deleteStatus = LoadDataStatus.initial,\n    this.toggleStatus = LoadDataStatus.initial,\n    this.currentTags = const [],\n    this.currentLevel = 1,\n    this.history = const [],\n  });\n\n  TagTreeState copyWith({\n    LoadDataStatus? status,\n    List<TagTree>? tags,\n    LoadDataStatus? deleteStatus,\n    LoadDataStatus? toggleStatus,\n    List<TagTree>? currentTags,\n    String? title,\n    int? currentLevel,\n    List<List<TagTree>>? history\n  }) {\n    return TagTreeState(\n      status: status ?? this.status,\n      tags: tags ?? this.tags,\n      deleteStatus: deleteStatus ?? this.deleteStatus,\n      toggleStatus: toggleStatus ?? this.toggleStatus,\n      currentTags: currentTags ?? this.currentTags,\n      currentLevel: currentLevel ?? this.currentLevel,\n      history: history ?? this.history\n    );\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/data/models/tag.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\nimport '/tags/data/models/tag_tree.dart';\n\npart 'tag.g.dart';\n\n@JsonSerializable()\nclass Tag extends Equatable {\n\n  final int id;\n  final String name;\n  final String? notes;\n  final bool enable;\n  final int? parentId;\n  final String? parentName;\n  final bool expenseable;\n  final bool incomeable;\n  final bool transferable;\n\n  Tag({\n    required this.id,\n    required this.name,\n    required this.enable,\n    this.parentId,\n    this.parentName,\n    required this.expenseable,\n    required this.incomeable,\n    required this.transferable,\n    this.notes\n  });\n\n  factory Tag.fromJson(Map<String, dynamic> json) => _$TagFromJson(json);\n\n  Map<String, dynamic> toJson() => _$TagToJson(this);\n\n  factory Tag.fromTree(TagTree tagTree) => Tag(\n      id: tagTree.id,\n      name: tagTree.name,\n      notes: tagTree.notes,\n      enable: tagTree.enable,\n      parentId: tagTree.parentId,\n      parentName: tagTree.parentName,\n      expenseable: tagTree.expenseable,\n      incomeable: tagTree.incomeable,\n      transferable: tagTree.transferable\n  );\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/data/models/tag.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'tag.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nTag _$TagFromJson(Map<String, dynamic> json) => Tag(\n      id: json['id'] as int,\n      name: json['name'] as String,\n      enable: json['enable'] as bool,\n      parentId: json['parentId'] as int?,\n      parentName: json['parentName'] as String?,\n      expenseable: json['expenseable'] as bool,\n      incomeable: json['incomeable'] as bool,\n      transferable: json['transferable'] as bool,\n      notes: json['notes'] as String?,\n    );\n\nMap<String, dynamic> _$TagToJson(Tag instance) => <String, dynamic>{\n      'id': instance.id,\n      'name': instance.name,\n      'notes': instance.notes,\n      'enable': instance.enable,\n      'parentId': instance.parentId,\n      'parentName': instance.parentName,\n      'expenseable': instance.expenseable,\n      'incomeable': instance.incomeable,\n      'transferable': instance.transferable,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/data/models/tag_form_request.dart",
    "content": "import 'package:json_annotation/json_annotation.dart';\nimport 'package:equatable/equatable.dart';\n\npart 'tag_form_request.g.dart';\n\n@JsonSerializable()\nclass TagFormRequest extends Equatable {\n\n  final String? name;\n  final String? notes;\n  final String? parentId;\n  final bool? expenseable;\n  final bool? incomeable;\n  final bool? transferable;\n\n  const TagFormRequest({\n    this.name,\n    this.notes,\n    this.parentId,\n    this.expenseable,\n    this.incomeable,\n    this.transferable\n  });\n\n  Map<String, dynamic> toJson() => _$TagFormRequestToJson(this);\n\n  TagFormRequest copyWith({\n    String? name,\n    String? notes,\n    String? parentId,\n    bool? expenseable,\n    bool? incomeable,\n    bool? transferable,\n  }) {\n    return TagFormRequest(\n      name: name ?? this.name,\n      notes: notes ?? this.notes,\n      parentId: parentId ?? this.parentId,\n      expenseable: expenseable ?? this.expenseable,\n      incomeable: incomeable ?? this.incomeable,\n      transferable: transferable ?? this.transferable,\n    );\n  }\n\n  @override\n  List<Object?> get props => [name, notes, parentId, expenseable, incomeable, transferable];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/data/models/tag_form_request.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'tag_form_request.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nTagFormRequest _$TagFormRequestFromJson(Map<String, dynamic> json) =>\n    TagFormRequest(\n      name: json['name'] as String?,\n      notes: json['notes'] as String?,\n      parentId: json['parentId'] as String?,\n      expenseable: json['expenseable'] as bool?,\n      incomeable: json['incomeable'] as bool?,\n      transferable: json['transferable'] as bool?,\n    );\n\nMap<String, dynamic> _$TagFormRequestToJson(TagFormRequest instance) =>\n    <String, dynamic>{\n      'name': instance.name,\n      'notes': instance.notes,\n      'parentId': instance.parentId,\n      'expenseable': instance.expenseable,\n      'incomeable': instance.incomeable,\n      'transferable': instance.transferable,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/data/models/tag_tree.dart",
    "content": "import 'package:equatable/equatable.dart';\nimport 'package:json_annotation/json_annotation.dart';\n\npart 'tag_tree.g.dart';\n\n@JsonSerializable()\nclass TagTree extends Equatable {\n\n  final int id;\n  final String name;\n  final String? notes;\n  final bool enable;\n  final int? parentId;\n  final String? parentName;\n  final List<TagTree>? children;\n  final bool expenseable;\n  final bool incomeable;\n  final bool transferable;\n\n  TagTree({\n    required this.id,\n    required this.name,\n    this.notes,\n    required this.enable,\n    this.parentId,\n    this.parentName,\n    this.children,\n    required this.expenseable,\n    required this.incomeable,\n    required this.transferable,\n  });\n\n  factory TagTree.fromJson(Map<String, dynamic> json) => _$TagTreeFromJson(json);\n\n  Map<String, dynamic> toJson() => _$TagTreeToJson(this);\n\n  @override\n  List<Object> get props => [id];\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/data/models/tag_tree.g.dart",
    "content": "// GENERATED CODE - DO NOT MODIFY BY HAND\n\npart of 'tag_tree.dart';\n\n// **************************************************************************\n// JsonSerializableGenerator\n// **************************************************************************\n\nTagTree _$TagTreeFromJson(Map<String, dynamic> json) => TagTree(\n      id: json['id'] as int,\n      name: json['name'] as String,\n      notes: json['notes'] as String?,\n      enable: json['enable'] as bool,\n      parentId: json['parentId'] as int?,\n      parentName: json['parentName'] as String?,\n      children: (json['children'] as List<dynamic>?)\n          ?.map((e) => TagTree.fromJson(e as Map<String, dynamic>))\n          .toList(),\n      expenseable: json['expenseable'] as bool,\n      incomeable: json['incomeable'] as bool,\n      transferable: json['transferable'] as bool,\n    );\n\nMap<String, dynamic> _$TagTreeToJson(TagTree instance) => <String, dynamic>{\n      'id': instance.id,\n      'name': instance.name,\n      'notes': instance.notes,\n      'enable': instance.enable,\n      'parentId': instance.parentId,\n      'parentName': instance.parentName,\n      'children': instance.children,\n      'expenseable': instance.expenseable,\n      'incomeable': instance.incomeable,\n      'transferable': instance.transferable,\n    };\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/data/tag_repository.dart",
    "content": "import 'dart:convert';\n\nimport '/commons/commons.dart';\nimport '/tags/tags.dart';\n\nclass TagRepository {\n\n  List<Tag> _responseToList(String response) {\n    return (json.decode(response)['data']).map<Tag>((i) => Tag.fromJson(i)).toList();\n  }\n\n  Future<List<Tag>> getExpenseable() async {\n    String response = await HttpClient().get('tags/expenseable');\n    return _responseToList(response);\n  }\n\n  Future<List<Tag>> getIncomeable() async {\n    String response = await HttpClient().get('tags/incomeable');\n    return _responseToList(response);\n  }\n\n  Future<List<Tag>> getTransferable() async {\n    String response = await HttpClient().get('tags/transferable');\n    return _responseToList(response);\n  }\n\n  Future<List<Tag>> getEnable() async {\n    String response = await HttpClient().get('tags/enable');\n    return _responseToList(response);\n  }\n\n  Future<Tag> get(int id) async {\n    String response = await HttpClient().get('tags/$id');\n    return Tag.fromJson(json.decode(response)['data']);\n  }\n\n  Future<List<TagTree>> query() async {\n    String response = await HttpClient().get('tags');\n    return (json.decode(response)['data']).map<TagTree>((i) => TagTree.fromJson(i)).toList();\n  }\n\n  Future<bool> toggle(String id) async {\n    String response = await HttpClient().put('tags/$id/toggle');\n    return parseResponse(response);\n  }\n\n  Future<bool> delete(String id) async {\n    String response = await HttpClient().delete('tags/$id');\n    return parseResponse(response);\n  }\n\n  Future<bool> add(TagFormRequest request) async {\n    String response = await HttpClient().post('tags', data: request.toJson());\n    return parseResponse(response);\n  }\n\n  Future<bool> update(int id, TagFormRequest request) async {\n    String response = await HttpClient().put('tags/$id', data: request.toJson());\n    return parseResponse(response);\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/tags.dart",
    "content": "export 'bloc/tag_expenseable/tag_expenseable_bloc.dart';\nexport 'bloc/tag_incomeable/tag_incomeable_bloc.dart';\nexport 'bloc/tag_transferable/tag_transferable_bloc.dart';\nexport 'bloc/tag_enable/tag_enable_bloc.dart';\nexport 'bloc/tag_tree/tag_tree_bloc.dart';\nexport 'bloc/tag_fetch/tag_fetch_bloc.dart';\nexport 'bloc/tag_form/tag_form_bloc.dart';\nexport 'data/tag_repository.dart';\nexport 'data/models/tag.dart';\nexport 'data/models/tag_tree.dart';\nexport 'data/models/tag_form_request.dart';\nexport 'ui/tags_page.dart';\nexport 'ui/tag_detail_page.dart';\nexport 'ui/tag_form_page.dart';\nexport 'ui/widgets/tag_form/name_input.dart';\nexport 'ui/widgets/tag_form/notes_input.dart';\nexport 'ui/widgets/tag_form/expenseable_input.dart';\nexport 'ui/widgets/tag_form/incomeable_input.dart';\nexport 'ui/widgets/tag_form/transferable_input.dart';\nexport 'ui/widgets/tag_form/parent_input.dart';"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/tag_detail_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter/services.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/tags/tags.dart';\n\nclass TagDetailPage extends StatefulWidget {\n\n  final Tag tag;\n  TagDetailPage({\n    required this.tag\n  });\n\n  @override\n  State<TagDetailPage> createState() => _TagDetailPageState();\n}\n\nclass _TagDetailPageState extends State<TagDetailPage> {\n\n  @override\n  void initState() {\n    BlocProvider.of<TagFetchBloc>(context).add(TagLoadDefault(tag: widget.tag));\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<TagTreeBloc, TagTreeState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              // Navigator.pop(context);\n              if (Navigator.canPop(context)) {\n                Navigator.of(context).pop();\n              } else {\n                SystemNavigator.pop();\n              }\n            }\n          }\n        ),\n        BlocListener<TagTreeBloc, TagTreeState>(\n          listenWhen: (previous, current) => previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              Message.success('操作成功！');\n              BlocProvider.of<TagFetchBloc>(context).add(TagFetched());\n            }\n          },\n        )\n      ],\n      child: BlocBuilder<TagFetchBloc, TagFetchState>(\n        builder: (context, state) {\n          return Scaffold(\n            appBar: AppBar(\n              centerTitle: true,\n              title: const Text('标签详情'),\n              actions: _buildActions(context, state.tag ?? widget.tag)\n            ),\n            body: Builder(\n              builder: (context) {\n                switch (state.status) {\n                  case LoadDataStatus.progress:\n                  case LoadDataStatus.initial:\n                    return const PageLoading();\n                  case LoadDataStatus.success:\n                    return _buildBody(context, state.tag ?? widget.tag);\n                  default:\n                    return PageError(onTap: () { BlocProvider.of<TagFetchBloc>(context).add(TagFetched()); });\n                }\n              },\n            )\n          );\n        }\n      )\n    );\n  }\n\n  List<Widget> _buildActions(BuildContext context, Tag tag) {\n    return [\n      IconButton(\n        icon: const Icon(Icons.add),\n        onPressed: () {\n          fullDialog(context, TagFormPage(type: 1, tag: tag));\n        }\n      ),\n      IconButton(\n        icon: Icon(Icons.edit),\n        onPressed: () {\n          fullDialog(context, TagFormPage(type: 2, tag: tag));\n        }\n      ),\n      IconButton(\n        icon: Icon(Icons.delete),\n        onPressed: () async {\n          if (await confirm(\n            context,\n            content: Text(\"确定删除${tag.name}吗？\"),\n            textOK: Text(\"确定\"),\n            textCancel: Text(\"取消\"),\n          )) {\n            BlocProvider.of<TagTreeBloc>(context).add(TagDeleted(tag.id.toString()));\n          }\n        }\n      )\n    ];\n  }\n\n  Widget _buildBody(BuildContext context, Tag tag) {\n    final theme = Theme.of(context);\n    TextStyle? style1 = theme.textTheme.bodyText2;\n    TextStyle? style2 = theme.textTheme.bodyText1;\n    return SingleChildScrollView(\n      child: Padding(\n        padding: EdgeInsets.symmetric(horizontal: 15),\n        child: Column(\n          children: [\n            SizedBox(height: 20),\n            Row(children: [Text(\"父级名称：\", style: style1), Text(tag.parentName ?? '', style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"标签名称：\", style: style1), Text(tag.name, style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"备注：\", style: style1), Flexible(child: Text(tag.notes ?? '', style: style2))]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可支出：\", style: style1), Text(boolToString(tag.expenseable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可收入：\", style: style1), Text(boolToString(tag.incomeable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可转账：\", style: style1), Text(boolToString(tag.transferable), style: style2)]),\n            SizedBox(height: 15),\n            Row(children: [Text(\"是否可用：\", style: style1), Text(boolToString(tag.enable), style: style2)]),\n            SizedBox(height: 20),\n            SizedBox(\n              width: double.infinity,\n              child: ElevatedButton(\n                  child: Text(tag.enable ? '禁用' : '启用'),\n                  onPressed: () {\n                    BlocProvider.of<TagTreeBloc>(context).add(TagToggled(tag.id.toString()));\n                  }\n              ),\n            )\n          ],\n        )\n      ),\n    );\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/tag_form_page.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:formz/formz.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/tags/tags.dart';\n\nclass TagFormPage extends StatelessWidget {\n\n  final int type; // 1-新增，2-修改\n  final Tag? tag;\n\n  const TagFormPage({\n    required this.type,\n    this.tag,\n  });\n\n  @override\n  Widget build(BuildContext context) {\n    return BlocProvider(\n      create: (_) => TagFormBloc(\n        tagRepository: RepositoryProvider.of<TagRepository>(context),\n        tagExpenseableBloc: BlocProvider.of<TagExpenseableBloc>(context),\n        tagIncomeableBloc: BlocProvider.of<TagIncomeableBloc>(context),\n        tagTransferableBloc: BlocProvider.of<TagTransferableBloc>(context),\n        tagEnableBloc: BlocProvider.of<TagEnableBloc>(context),\n      )..add(TagFormDefaultLoaded(type, tag)),\n      child: BlocListener<TagFormBloc, TagFormState>(\n        listener: (context, state) {\n          if (state.status == FormzStatus.submissionSuccess) {\n            Message.success('操作成功');\n            Navigator.of(context).pop();\n            BlocProvider.of<TagTreeBloc>(context).add(TagTreeRefreshed());\n            if (type == 2) {\n              BlocProvider.of<TagFetchBloc>(context).add(TagFetched());\n            }\n          }\n        },\n        child: Builder(\n          builder: (context) {\n            return Scaffold(\n              appBar: AppBar(\n                centerTitle: true,\n                title: Text(_buildTitle(type)),\n                actions: [\n                  IconButton(\n                    icon: Icon(Icons.done),\n                    onPressed: () {\n                      BlocProvider.of<TagFormBloc>(context).add(TagFormSubmitted(type, tag));\n                    }\n                  )\n                ]\n              ),\n              body: SingleChildScrollView(\n                child: Padding(\n                  padding: EdgeInsets.symmetric(horizontal: 15),\n                  child: Column(\n                    children: [\n                      ParentInput(),\n                      SizedBox(height: 10),\n                      NameInput(),\n                      SizedBox(height: 10),\n                      ExpenseableSwitch(),\n                      SizedBox(height: 10),\n                      IncomeableSwitch(),\n                      SizedBox(height: 10),\n                      TransferableSwitch(),\n                      SizedBox(height: 10),\n                      NotesInput()\n                    ],\n                  ),\n                ),\n              )\n            );\n          },\n        )\n      )\n    );\n  }\n\n  String _buildTitle(int type) {\n    switch (type) {\n      case 1:\n        return '新增标签';\n      case 2:\n        return '修改标签';\n      default:\n        return '操作类型异常';\n    }\n  }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/tags_page.dart",
    "content": "import 'package:flutter/cupertino.dart';\nimport 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/routes.dart';\nimport '/components/components.dart';\nimport '/commons/commons.dart';\nimport '/tags/tags.dart';\n\nclass TagsPage extends StatefulWidget {\n  @override\n  State<TagsPage> createState() => _TagPagesState();\n}\n\nclass _TagPagesState extends State<TagsPage> {\n\n  @override\n  void initState() {\n    BlocProvider.of<TagTreeBloc>(context).add(TagTreeRefreshed());\n    super.initState();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    final state = context.watch<TagTreeBloc>().state;\n    return MultiBlocListener(\n      listeners: [\n        BlocListener<TagTreeBloc, TagTreeState>(\n          listenWhen: (previous, current) => previous.deleteStatus != current.deleteStatus,\n          listener: (context, state) {\n            if (state.deleteStatus == LoadDataStatus.success) {\n              BlocProvider.of<TagTreeBloc>(context).add(TagTreeRefreshed());\n            }\n          },\n        ),\n        BlocListener<TagTreeBloc, TagTreeState>(\n          listenWhen: (previous, current) => previous.toggleStatus != current.toggleStatus,\n          listener: (context, state) {\n            if (state.toggleStatus == LoadDataStatus.success) {\n              BlocProvider.of<TagTreeBloc>(context).add(TagTreeRefreshed());\n            }\n          },\n        ),\n      ],\n      child: WillPopScope(\n        onWillPop: () => _onWillPop(state),\n        child: BlocBuilder<TagTreeBloc, TagTreeState>(\n          builder: (context, state) {\n            return Scaffold(\n              appBar: AppBar(\n                title: const Text('标签'),\n                centerTitle: true,\n                actions: [\n                  IconButton(\n                    icon: const Icon(Icons.add),\n                    onPressed: () {\n                      fullDialog(context, TagFormPage(type: 1));\n                    }\n                  )\n                ]\n              ),\n              body: Builder(\n                builder: (BuildContext context) {\n                  switch (state.status) {\n                    case LoadDataStatus.progress:\n                    case LoadDataStatus.initial:\n                      return const PageLoading();\n                    case LoadDataStatus.success:\n                      if (state.currentTags.isEmpty) return Empty();\n                      return _buildList(context, state.currentTags);\n                    default:\n                      return PageError(onTap: () { BlocProvider.of<TagTreeBloc>(context).add(TagTreeRefreshed()); });\n                  }\n                },\n              ),\n            );\n          },\n        )\n      ),\n    );\n  }\n\n  Widget _buildList(BuildContext context, List<TagTree> tags) {\n    final theme = Theme.of(context);\n    return ListView.separated(\n      itemCount: tags.length,\n      itemBuilder: (context, index) {\n        TagTree tagTree = tags[index];\n        return ListTile(\n          dense: true,\n          title: Text(tagTree.name, style: theme.textTheme.bodyText1),\n          subtitle: tagTree.notes != null && tagTree.notes!.isNotEmpty ? Text(tagTree.notes!, style: theme.textTheme.caption) : null,\n          trailing: tagTree.children != null && tagTree.children!.isNotEmpty ? Icon(Icons.keyboard_arrow_right) : null,\n          onTap: () {\n            if (tagTree.children != null && tagTree.children!.isNotEmpty) {\n              BlocProvider.of<TagTreeBloc>(context).add(TagItemClicked(tagTree: tagTree));\n            } else {\n              Navigator.pushNamed(context, '/tag-detail', arguments: TagDetailArguments(tag: Tag.fromTree(tagTree)));\n            }\n          },\n          onLongPress: () {\n            Navigator.pushNamed(context, '/tag-detail', arguments: TagDetailArguments(tag: Tag.fromTree(tagTree)));\n          },\n        );\n      },\n      separatorBuilder: (context, index) {\n        return Divider();\n      },\n    );\n  }\n\n  Future<bool> _onWillPop(state) async {\n    if (state.currentLevel == 1) return true;\n    BlocProvider.of<TagTreeBloc>(context).add(TagBackClicked());\n    return false;\n  }\n\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/widgets/tag_form/expenseable_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/tags/tags.dart';\nimport '/commons/commons.dart';\n\nclass ExpenseableSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可支出\",\n        BlocSelector<TagFormBloc, TagFormState, bool?>(\n          selector: (state) => state.request.expenseable,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<TagFormBloc>().add(TagFormExpenseableChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/widgets/tag_form/incomeable_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\n\nimport '/tags/tags.dart';\nimport '/commons/commons.dart';\n\nclass IncomeableSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可收入\",\n        BlocSelector<TagFormBloc, TagFormState, bool?>(\n          selector: (state) => state.request.incomeable,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<TagFormBloc>().add(TagFormIncomeableChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/widgets/tag_form/name_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/commons/commons.dart';\nimport '/tags/tags.dart';\n\nclass NameInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n          '名称',\n          BlocBuilder<TagFormBloc, TagFormState>(\n            buildWhen: (previous, current) => previous.request.name != current.request.name,\n            builder: (context, state) {\n              controller.value = TextEditingValue(\n                text: state.request.name ?? '',\n                selection: TextSelection.fromPosition(\n                  TextPosition(offset: state.request.name?.length ?? 0),\n                ),\n              );\n              return\n                TextField(\n                  controller: controller,\n                  onChanged: (value) => context.read<TagFormBloc>().add(TagFormNameChanged(value)),\n                  decoration: InputDecoration(\n                    contentPadding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0),\n                  ),\n                );\n            }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/widgets/tag_form/notes_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/tags/tags.dart';\nimport '/commons/commons.dart';\n\nclass NotesInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    var controller = TextEditingController();\n    return\n      buildFormItem(\n          '备注',\n          BlocBuilder<TagFormBloc, TagFormState>(\n              buildWhen: (previous, current) => previous.request.notes != current.request.notes,\n              builder: (context, state) {\n                controller.value = TextEditingValue(\n                  text: state.request.notes ?? '',\n                  selection: TextSelection.fromPosition(\n                    TextPosition(offset: state.request.notes?.length ?? 0),\n                  ),\n                );\n                return\n                  TextField(\n                    controller: controller,\n                    onChanged: (value) => context.read<TagFormBloc>().add(TagFormNotesChanged(value)),\n                    decoration: InputDecoration(),\n                  );\n              }\n          ), context\n      );\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/widgets/tag_form/parent_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport 'package:awesome_select/awesome_select.dart';\nimport '/commons/commons.dart';\nimport '/tags/tags.dart';\n\nclass ParentInput extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    final state1 = context.watch<TagEnableBloc>().state;\n    return BlocBuilder<TagFormBloc, TagFormState>(\n      buildWhen: (previous, current) => previous.request.parentId != current.request.parentId,\n      builder: (context, state) {\n        return SmartSelect<String>.single\n          (\n            title: '父级标签',\n            selectedValue: state.request.parentId ?? '',\n            onChange: (selected) {\n              context.read<TagFormBloc>().add(TagFormParentChanged(selected.value ?? ''));\n            },\n            choiceItems: state1 is TagEnableStateLoadSuccess ? modelToChoice(state1.tags) : [],\n            choiceType: S2ChoiceType.chips,\n            modalFilter: true,\n            modalFilterAuto: true,\n            tileBuilder: (context, state) {\n              return S2Tile.fromState(\n                state,\n                isLoading: state1 is TagEnableStateLoadInProgress,\n                padding: EdgeInsets.zero,\n              );\n            }\n        );\n      }\n    );\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/tags/ui/widgets/tag_form/transferable_input.dart",
    "content": "import 'package:flutter/material.dart';\nimport 'package:flutter_bloc/flutter_bloc.dart';\nimport '/tags/tags.dart';\nimport '/commons/commons.dart';\n\nclass TransferableSwitch extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return\n      buildFormItem(\n        \"是否可转账\",\n        BlocSelector<TagFormBloc, TagFormState, bool?>(\n          selector: (state) => state.request.transferable,\n          builder: (context, state) {\n            return\n              Align(\n                alignment: Alignment.centerLeft,\n                child: Switch(\n                  value: state ?? true,\n                  onChanged: (value) => context.read<TagFormBloc>().add(TagFormTransferableChanged(value)),\n                ),\n              );\n          }\n        ), context);\n  }\n}"
  },
  {
    "path": "bookkeeping_user_flutter/lib/themes.dart",
    "content": "import 'package:flutter/material.dart';\n\nfinal ThemeData lightTheme = _buildLightTheme();\nfinal ThemeData darkTheme = _buildDarkTheme();\n\nThemeData _buildLightTheme() {\n  final base = ThemeData.light();\n  return base.copyWith(\n    scaffoldBackgroundColor: Colors.white,\n    chipTheme: ChipThemeData.fromDefaults(\n      secondaryColor: Colors.red,\n      brightness: Brightness.light,\n      labelStyle: TextStyle(fontSize: 16),\n    )\n  );\n}\n\nThemeData _buildDarkTheme() {\n  final base = ThemeData.dark();\n  return base.copyWith(\n\n  );\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/.gitignore",
    "content": "flutter/ephemeral\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/CMakeLists.txt",
    "content": "# Project-level configuration.\ncmake_minimum_required(VERSION 3.10)\nproject(runner LANGUAGES CXX)\n\n# The name of the executable created for the application. Change this to change\n# the on-disk name of your application.\nset(BINARY_NAME \"bookkeeping_user_flutter\")\n# The unique GTK application identifier for this application. See:\n# https://wiki.gnome.org/HowDoI/ChooseApplicationID\nset(APPLICATION_ID \"com.jiukuaitech.bookkeeping_user_flutter\")\n\n# Explicitly opt in to modern CMake behaviors to avoid warnings with recent\n# versions of CMake.\ncmake_policy(SET CMP0063 NEW)\n\n# Load bundled libraries from the lib/ directory relative to the binary.\nset(CMAKE_INSTALL_RPATH \"$ORIGIN/lib\")\n\n# Root filesystem for cross-building.\nif(FLUTTER_TARGET_PLATFORM_SYSROOT)\n  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})\n  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})\n  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\n  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\n  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\nendif()\n\n# Define build configuration options.\nif(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)\n  set(CMAKE_BUILD_TYPE \"Debug\" CACHE\n    STRING \"Flutter build mode\" FORCE)\n  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS\n    \"Debug\" \"Profile\" \"Release\")\nendif()\n\n# Compilation settings that should be applied to most targets.\n#\n# Be cautious about adding new options here, as plugins use this function by\n# default. In most cases, you should add new options to specific targets instead\n# of modifying this function.\nfunction(APPLY_STANDARD_SETTINGS TARGET)\n  target_compile_features(${TARGET} PUBLIC cxx_std_14)\n  target_compile_options(${TARGET} PRIVATE -Wall -Werror)\n  target_compile_options(${TARGET} PRIVATE \"$<$<NOT:$<CONFIG:Debug>>:-O3>\")\n  target_compile_definitions(${TARGET} PRIVATE \"$<$<NOT:$<CONFIG:Debug>>:NDEBUG>\")\nendfunction()\n\n# Flutter library and tool build rules.\nset(FLUTTER_MANAGED_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/flutter\")\nadd_subdirectory(${FLUTTER_MANAGED_DIR})\n\n# System-level dependencies.\nfind_package(PkgConfig REQUIRED)\npkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)\n\nadd_definitions(-DAPPLICATION_ID=\"${APPLICATION_ID}\")\n\n# Define the application target. To change its name, change BINARY_NAME above,\n# not the value here, or `flutter run` will no longer work.\n#\n# Any new source files that you add to the application should be added here.\nadd_executable(${BINARY_NAME}\n  \"main.cc\"\n  \"my_application.cc\"\n  \"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc\"\n)\n\n# Apply the standard set of build settings. This can be removed for applications\n# that need different build settings.\napply_standard_settings(${BINARY_NAME})\n\n# Add dependency libraries. Add any application-specific dependencies here.\ntarget_link_libraries(${BINARY_NAME} PRIVATE flutter)\ntarget_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)\n\n# Run the Flutter tool portions of the build. This must not be removed.\nadd_dependencies(${BINARY_NAME} flutter_assemble)\n\n# Only the install-generated bundle's copy of the executable will launch\n# correctly, since the resources must in the right relative locations. To avoid\n# people trying to run the unbundled copy, put it in a subdirectory instead of\n# the default top-level location.\nset_target_properties(${BINARY_NAME}\n  PROPERTIES\n  RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_BINARY_DIR}/intermediates_do_not_run\"\n)\n\n# Generated plugin build rules, which manage building the plugins and adding\n# them to the application.\ninclude(flutter/generated_plugins.cmake)\n\n\n# === Installation ===\n# By default, \"installing\" just makes a relocatable bundle in the build\n# directory.\nset(BUILD_BUNDLE_DIR \"${PROJECT_BINARY_DIR}/bundle\")\nif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)\n  set(CMAKE_INSTALL_PREFIX \"${BUILD_BUNDLE_DIR}\" CACHE PATH \"...\" FORCE)\nendif()\n\n# Start with a clean build bundle directory every time.\ninstall(CODE \"\n  file(REMOVE_RECURSE \\\"${BUILD_BUNDLE_DIR}/\\\")\n  \" COMPONENT Runtime)\n\nset(INSTALL_BUNDLE_DATA_DIR \"${CMAKE_INSTALL_PREFIX}/data\")\nset(INSTALL_BUNDLE_LIB_DIR \"${CMAKE_INSTALL_PREFIX}/lib\")\n\ninstall(TARGETS ${BINARY_NAME} RUNTIME DESTINATION \"${CMAKE_INSTALL_PREFIX}\"\n  COMPONENT Runtime)\n\ninstall(FILES \"${FLUTTER_ICU_DATA_FILE}\" DESTINATION \"${INSTALL_BUNDLE_DATA_DIR}\"\n  COMPONENT Runtime)\n\ninstall(FILES \"${FLUTTER_LIBRARY}\" DESTINATION \"${INSTALL_BUNDLE_LIB_DIR}\"\n  COMPONENT Runtime)\n\nforeach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})\n  install(FILES \"${bundled_library}\"\n    DESTINATION \"${INSTALL_BUNDLE_LIB_DIR}\"\n    COMPONENT Runtime)\nendforeach(bundled_library)\n\n# Fully re-copy the assets directory on each build to avoid having stale files\n# from a previous install.\nset(FLUTTER_ASSET_DIR_NAME \"flutter_assets\")\ninstall(CODE \"\n  file(REMOVE_RECURSE \\\"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\\\")\n  \" COMPONENT Runtime)\ninstall(DIRECTORY \"${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}\"\n  DESTINATION \"${INSTALL_BUNDLE_DATA_DIR}\" COMPONENT Runtime)\n\n# Install the AOT library on non-Debug builds only.\nif(NOT CMAKE_BUILD_TYPE MATCHES \"Debug\")\n  install(FILES \"${AOT_LIBRARY}\" DESTINATION \"${INSTALL_BUNDLE_LIB_DIR}\"\n    COMPONENT Runtime)\nendif()\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/flutter/CMakeLists.txt",
    "content": "# This file controls Flutter-level build steps. It should not be edited.\ncmake_minimum_required(VERSION 3.10)\n\nset(EPHEMERAL_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/ephemeral\")\n\n# Configuration provided via flutter tool.\ninclude(${EPHEMERAL_DIR}/generated_config.cmake)\n\n# TODO: Move the rest of this into files in ephemeral. See\n# https://github.com/flutter/flutter/issues/57146.\n\n# Serves the same purpose as list(TRANSFORM ... PREPEND ...),\n# which isn't available in 3.10.\nfunction(list_prepend LIST_NAME PREFIX)\n    set(NEW_LIST \"\")\n    foreach(element ${${LIST_NAME}})\n        list(APPEND NEW_LIST \"${PREFIX}${element}\")\n    endforeach(element)\n    set(${LIST_NAME} \"${NEW_LIST}\" PARENT_SCOPE)\nendfunction()\n\n# === Flutter Library ===\n# System-level dependencies.\nfind_package(PkgConfig REQUIRED)\npkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)\npkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)\npkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)\n\nset(FLUTTER_LIBRARY \"${EPHEMERAL_DIR}/libflutter_linux_gtk.so\")\n\n# Published to parent scope for install step.\nset(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)\nset(FLUTTER_ICU_DATA_FILE \"${EPHEMERAL_DIR}/icudtl.dat\" PARENT_SCOPE)\nset(PROJECT_BUILD_DIR \"${PROJECT_DIR}/build/\" PARENT_SCOPE)\nset(AOT_LIBRARY \"${PROJECT_DIR}/build/lib/libapp.so\" PARENT_SCOPE)\n\nlist(APPEND FLUTTER_LIBRARY_HEADERS\n  \"fl_basic_message_channel.h\"\n  \"fl_binary_codec.h\"\n  \"fl_binary_messenger.h\"\n  \"fl_dart_project.h\"\n  \"fl_engine.h\"\n  \"fl_json_message_codec.h\"\n  \"fl_json_method_codec.h\"\n  \"fl_message_codec.h\"\n  \"fl_method_call.h\"\n  \"fl_method_channel.h\"\n  \"fl_method_codec.h\"\n  \"fl_method_response.h\"\n  \"fl_plugin_registrar.h\"\n  \"fl_plugin_registry.h\"\n  \"fl_standard_message_codec.h\"\n  \"fl_standard_method_codec.h\"\n  \"fl_string_codec.h\"\n  \"fl_value.h\"\n  \"fl_view.h\"\n  \"flutter_linux.h\"\n)\nlist_prepend(FLUTTER_LIBRARY_HEADERS \"${EPHEMERAL_DIR}/flutter_linux/\")\nadd_library(flutter INTERFACE)\ntarget_include_directories(flutter INTERFACE\n  \"${EPHEMERAL_DIR}\"\n)\ntarget_link_libraries(flutter INTERFACE \"${FLUTTER_LIBRARY}\")\ntarget_link_libraries(flutter INTERFACE\n  PkgConfig::GTK\n  PkgConfig::GLIB\n  PkgConfig::GIO\n)\nadd_dependencies(flutter flutter_assemble)\n\n# === Flutter tool backend ===\n# _phony_ is a non-existent file to force this command to run every time,\n# since currently there's no way to get a full input/output list from the\n# flutter tool.\nadd_custom_command(\n  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}\n    ${CMAKE_CURRENT_BINARY_DIR}/_phony_\n  COMMAND ${CMAKE_COMMAND} -E env\n    ${FLUTTER_TOOL_ENVIRONMENT}\n    \"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh\"\n      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}\n  VERBATIM\n)\nadd_custom_target(flutter_assemble DEPENDS\n  \"${FLUTTER_LIBRARY}\"\n  ${FLUTTER_LIBRARY_HEADERS}\n)\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/flutter/generated_plugin_registrant.cc",
    "content": "//\n//  Generated file. Do not edit.\n//\n\n// clang-format off\n\n#include \"generated_plugin_registrant.h\"\n\n\nvoid fl_register_plugins(FlPluginRegistry* registry) {\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/flutter/generated_plugin_registrant.h",
    "content": "//\n//  Generated file. Do not edit.\n//\n\n// clang-format off\n\n#ifndef GENERATED_PLUGIN_REGISTRANT_\n#define GENERATED_PLUGIN_REGISTRANT_\n\n#include <flutter_linux/flutter_linux.h>\n\n// Registers Flutter plugins.\nvoid fl_register_plugins(FlPluginRegistry* registry);\n\n#endif  // GENERATED_PLUGIN_REGISTRANT_\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/flutter/generated_plugins.cmake",
    "content": "#\n# Generated file, do not edit.\n#\n\nlist(APPEND FLUTTER_PLUGIN_LIST\n)\n\nlist(APPEND FLUTTER_FFI_PLUGIN_LIST\n)\n\nset(PLUGIN_BUNDLED_LIBRARIES)\n\nforeach(plugin ${FLUTTER_PLUGIN_LIST})\n  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})\n  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})\nendforeach(plugin)\n\nforeach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})\n  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})\nendforeach(ffi_plugin)\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/main.cc",
    "content": "#include \"my_application.h\"\n\nint main(int argc, char** argv) {\n  g_autoptr(MyApplication) app = my_application_new();\n  return g_application_run(G_APPLICATION(app), argc, argv);\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/my_application.cc",
    "content": "#include \"my_application.h\"\n\n#include <flutter_linux/flutter_linux.h>\n#ifdef GDK_WINDOWING_X11\n#include <gdk/gdkx.h>\n#endif\n\n#include \"flutter/generated_plugin_registrant.h\"\n\nstruct _MyApplication {\n  GtkApplication parent_instance;\n  char** dart_entrypoint_arguments;\n};\n\nG_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)\n\n// Implements GApplication::activate.\nstatic void my_application_activate(GApplication* application) {\n  MyApplication* self = MY_APPLICATION(application);\n  GtkWindow* window =\n      GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));\n\n  // Use a header bar when running in GNOME as this is the common style used\n  // by applications and is the setup most users will be using (e.g. Ubuntu\n  // desktop).\n  // If running on X and not using GNOME then just use a traditional title bar\n  // in case the window manager does more exotic layout, e.g. tiling.\n  // If running on Wayland assume the header bar will work (may need changing\n  // if future cases occur).\n  gboolean use_header_bar = TRUE;\n#ifdef GDK_WINDOWING_X11\n  GdkScreen* screen = gtk_window_get_screen(window);\n  if (GDK_IS_X11_SCREEN(screen)) {\n    const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);\n    if (g_strcmp0(wm_name, \"GNOME Shell\") != 0) {\n      use_header_bar = FALSE;\n    }\n  }\n#endif\n  if (use_header_bar) {\n    GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());\n    gtk_widget_show(GTK_WIDGET(header_bar));\n    gtk_header_bar_set_title(header_bar, \"bookkeeping_user_flutter\");\n    gtk_header_bar_set_show_close_button(header_bar, TRUE);\n    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));\n  } else {\n    gtk_window_set_title(window, \"bookkeeping_user_flutter\");\n  }\n\n  gtk_window_set_default_size(window, 1280, 720);\n  gtk_widget_show(GTK_WIDGET(window));\n\n  g_autoptr(FlDartProject) project = fl_dart_project_new();\n  fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);\n\n  FlView* view = fl_view_new(project);\n  gtk_widget_show(GTK_WIDGET(view));\n  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));\n\n  fl_register_plugins(FL_PLUGIN_REGISTRY(view));\n\n  gtk_widget_grab_focus(GTK_WIDGET(view));\n}\n\n// Implements GApplication::local_command_line.\nstatic gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) {\n  MyApplication* self = MY_APPLICATION(application);\n  // Strip out the first argument as it is the binary name.\n  self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);\n\n  g_autoptr(GError) error = nullptr;\n  if (!g_application_register(application, nullptr, &error)) {\n     g_warning(\"Failed to register: %s\", error->message);\n     *exit_status = 1;\n     return TRUE;\n  }\n\n  g_application_activate(application);\n  *exit_status = 0;\n\n  return TRUE;\n}\n\n// Implements GObject::dispose.\nstatic void my_application_dispose(GObject* object) {\n  MyApplication* self = MY_APPLICATION(object);\n  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);\n  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);\n}\n\nstatic void my_application_class_init(MyApplicationClass* klass) {\n  G_APPLICATION_CLASS(klass)->activate = my_application_activate;\n  G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;\n  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;\n}\n\nstatic void my_application_init(MyApplication* self) {}\n\nMyApplication* my_application_new() {\n  return MY_APPLICATION(g_object_new(my_application_get_type(),\n                                     \"application-id\", APPLICATION_ID,\n                                     \"flags\", G_APPLICATION_NON_UNIQUE,\n                                     nullptr));\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/linux/my_application.h",
    "content": "#ifndef FLUTTER_MY_APPLICATION_H_\n#define FLUTTER_MY_APPLICATION_H_\n\n#include <gtk/gtk.h>\n\nG_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,\n                     GtkApplication)\n\n/**\n * my_application_new:\n *\n * Creates a new Flutter-based application.\n *\n * Returns: a new #MyApplication.\n */\nMyApplication* my_application_new();\n\n#endif  // FLUTTER_MY_APPLICATION_H_\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/.gitignore",
    "content": "# Flutter-related\n**/Flutter/ephemeral/\n**/Pods/\n\n# Xcode-related\n**/dgph\n**/xcuserdata/\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Flutter/Flutter-Debug.xcconfig",
    "content": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"\n#include \"ephemeral/Flutter-Generated.xcconfig\"\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Flutter/Flutter-Release.xcconfig",
    "content": "#include? \"Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"\n#include \"ephemeral/Flutter-Generated.xcconfig\"\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Flutter/GeneratedPluginRegistrant.swift",
    "content": "//\n//  Generated file. Do not edit.\n//\n\nimport FlutterMacOS\nimport Foundation\n\nimport shared_preferences_macos\n\nfunc RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {\n  SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: \"SharedPreferencesPlugin\"))\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Podfile",
    "content": "platform :osx, '10.11'\n\n# CocoaPods analytics sends network stats synchronously affecting flutter build latency.\nENV['COCOAPODS_DISABLE_STATS'] = 'true'\n\nproject 'Runner', {\n  'Debug' => :debug,\n  'Profile' => :release,\n  'Release' => :release,\n}\n\ndef flutter_root\n  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)\n  unless File.exist?(generated_xcode_build_settings_path)\n    raise \"#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \\\"flutter pub get\\\" is executed first\"\n  end\n\n  File.foreach(generated_xcode_build_settings_path) do |line|\n    matches = line.match(/FLUTTER_ROOT\\=(.*)/)\n    return matches[1].strip if matches\n  end\n  raise \"FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \\\"flutter pub get\\\"\"\nend\n\nrequire File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)\n\nflutter_macos_podfile_setup\n\ntarget 'Runner' do\n  use_frameworks!\n  use_modular_headers!\n\n  flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))\nend\n\npost_install do |installer|\n  installer.pods_project.targets.each do |target|\n    flutter_additional_macos_build_settings(target)\n  end\nend\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/AppDelegate.swift",
    "content": "import Cocoa\nimport FlutterMacOS\n\n@NSApplicationMain\nclass AppDelegate: FlutterAppDelegate {\n  override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {\n    return true\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_16.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"16x16\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_32.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"32x32\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_32.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"32x32\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_64.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"128x128\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_128.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"128x128\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_256.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"256x256\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_256.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"256x256\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_512.png\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"size\" : \"512x512\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_512.png\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"size\" : \"512x512\",\n      \"idiom\" : \"mac\",\n      \"filename\" : \"app_icon_1024.png\",\n      \"scale\" : \"2x\"\n    }\n  ],\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/Base.lproj/MainMenu.xib",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.XIB\" version=\"3.0\" toolsVersion=\"14490.70\" targetRuntime=\"MacOSX.Cocoa\" propertyAccessControl=\"none\" useAutolayout=\"YES\" customObjectInstantitationMethod=\"direct\">\n    <dependencies>\n        <deployment identifier=\"macosx\"/>\n        <plugIn identifier=\"com.apple.InterfaceBuilder.CocoaPlugin\" version=\"14490.70\"/>\n        <capability name=\"documents saved in the Xcode 8 format\" minToolsVersion=\"8.0\"/>\n    </dependencies>\n    <objects>\n        <customObject id=\"-2\" userLabel=\"File's Owner\" customClass=\"NSApplication\">\n            <connections>\n                <outlet property=\"delegate\" destination=\"Voe-Tx-rLC\" id=\"GzC-gU-4Uq\"/>\n            </connections>\n        </customObject>\n        <customObject id=\"-1\" userLabel=\"First Responder\" customClass=\"FirstResponder\"/>\n        <customObject id=\"-3\" userLabel=\"Application\" customClass=\"NSObject\"/>\n        <customObject id=\"Voe-Tx-rLC\" customClass=\"AppDelegate\" customModule=\"Runner\" customModuleProvider=\"target\">\n            <connections>\n                <outlet property=\"applicationMenu\" destination=\"uQy-DD-JDr\" id=\"XBo-yE-nKs\"/>\n                <outlet property=\"mainFlutterWindow\" destination=\"QvC-M9-y7g\" id=\"gIp-Ho-8D9\"/>\n            </connections>\n        </customObject>\n        <customObject id=\"YLy-65-1bz\" customClass=\"NSFontManager\"/>\n        <menu title=\"Main Menu\" systemMenu=\"main\" id=\"AYu-sK-qS6\">\n            <items>\n                <menuItem title=\"APP_NAME\" id=\"1Xt-HY-uBw\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"APP_NAME\" systemMenu=\"apple\" id=\"uQy-DD-JDr\">\n                        <items>\n                            <menuItem title=\"About APP_NAME\" id=\"5kV-Vb-QxS\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"orderFrontStandardAboutPanel:\" target=\"-1\" id=\"Exp-CZ-Vem\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"VOq-y0-SEH\"/>\n                            <menuItem title=\"Preferences…\" keyEquivalent=\",\" id=\"BOF-NM-1cW\"/>\n                            <menuItem isSeparatorItem=\"YES\" id=\"wFC-TO-SCJ\"/>\n                            <menuItem title=\"Services\" id=\"NMo-om-nkz\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Services\" systemMenu=\"services\" id=\"hz9-B4-Xy5\"/>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"4je-JR-u6R\"/>\n                            <menuItem title=\"Hide APP_NAME\" keyEquivalent=\"h\" id=\"Olw-nP-bQN\">\n                                <connections>\n                                    <action selector=\"hide:\" target=\"-1\" id=\"PnN-Uc-m68\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Hide Others\" keyEquivalent=\"h\" id=\"Vdr-fp-XzO\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"hideOtherApplications:\" target=\"-1\" id=\"VT4-aY-XCT\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Show All\" id=\"Kd2-mp-pUS\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"unhideAllApplications:\" target=\"-1\" id=\"Dhg-Le-xox\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"kCx-OE-vgT\"/>\n                            <menuItem title=\"Quit APP_NAME\" keyEquivalent=\"q\" id=\"4sb-4s-VLi\">\n                                <connections>\n                                    <action selector=\"terminate:\" target=\"-1\" id=\"Te7-pn-YzF\"/>\n                                </connections>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"Edit\" id=\"5QF-Oa-p0T\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"Edit\" id=\"W48-6f-4Dl\">\n                        <items>\n                            <menuItem title=\"Undo\" keyEquivalent=\"z\" id=\"dRJ-4n-Yzg\">\n                                <connections>\n                                    <action selector=\"undo:\" target=\"-1\" id=\"M6e-cu-g7V\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Redo\" keyEquivalent=\"Z\" id=\"6dh-zS-Vam\">\n                                <connections>\n                                    <action selector=\"redo:\" target=\"-1\" id=\"oIA-Rs-6OD\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"WRV-NI-Exz\"/>\n                            <menuItem title=\"Cut\" keyEquivalent=\"x\" id=\"uRl-iY-unG\">\n                                <connections>\n                                    <action selector=\"cut:\" target=\"-1\" id=\"YJe-68-I9s\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Copy\" keyEquivalent=\"c\" id=\"x3v-GG-iWU\">\n                                <connections>\n                                    <action selector=\"copy:\" target=\"-1\" id=\"G1f-GL-Joy\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Paste\" keyEquivalent=\"v\" id=\"gVA-U4-sdL\">\n                                <connections>\n                                    <action selector=\"paste:\" target=\"-1\" id=\"UvS-8e-Qdg\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Paste and Match Style\" keyEquivalent=\"V\" id=\"WeT-3V-zwk\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"pasteAsPlainText:\" target=\"-1\" id=\"cEh-KX-wJQ\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Delete\" id=\"pa3-QI-u2k\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"delete:\" target=\"-1\" id=\"0Mk-Ml-PaM\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Select All\" keyEquivalent=\"a\" id=\"Ruw-6m-B2m\">\n                                <connections>\n                                    <action selector=\"selectAll:\" target=\"-1\" id=\"VNm-Mi-diN\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"uyl-h8-XO2\"/>\n                            <menuItem title=\"Find\" id=\"4EN-yA-p0u\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Find\" id=\"1b7-l0-nxx\">\n                                    <items>\n                                        <menuItem title=\"Find…\" tag=\"1\" keyEquivalent=\"f\" id=\"Xz5-n4-O0W\">\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"cD7-Qs-BN4\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Find and Replace…\" tag=\"12\" keyEquivalent=\"f\" id=\"YEy-JH-Tfz\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\" option=\"YES\" command=\"YES\"/>\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"WD3-Gg-5AJ\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Find Next\" tag=\"2\" keyEquivalent=\"g\" id=\"q09-fT-Sye\">\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"NDo-RZ-v9R\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Find Previous\" tag=\"3\" keyEquivalent=\"G\" id=\"OwM-mh-QMV\">\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"HOh-sY-3ay\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Use Selection for Find\" tag=\"7\" keyEquivalent=\"e\" id=\"buJ-ug-pKt\">\n                                            <connections>\n                                                <action selector=\"performFindPanelAction:\" target=\"-1\" id=\"U76-nv-p5D\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Jump to Selection\" keyEquivalent=\"j\" id=\"S0p-oC-mLd\">\n                                            <connections>\n                                                <action selector=\"centerSelectionInVisibleArea:\" target=\"-1\" id=\"IOG-6D-g5B\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Spelling and Grammar\" id=\"Dv1-io-Yv7\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Spelling\" id=\"3IN-sU-3Bg\">\n                                    <items>\n                                        <menuItem title=\"Show Spelling and Grammar\" keyEquivalent=\":\" id=\"HFo-cy-zxI\">\n                                            <connections>\n                                                <action selector=\"showGuessPanel:\" target=\"-1\" id=\"vFj-Ks-hy3\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Check Document Now\" keyEquivalent=\";\" id=\"hz2-CU-CR7\">\n                                            <connections>\n                                                <action selector=\"checkSpelling:\" target=\"-1\" id=\"fz7-VC-reM\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"bNw-od-mp5\"/>\n                                        <menuItem title=\"Check Spelling While Typing\" id=\"rbD-Rh-wIN\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleContinuousSpellChecking:\" target=\"-1\" id=\"7w6-Qz-0kB\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Check Grammar With Spelling\" id=\"mK6-2p-4JG\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleGrammarChecking:\" target=\"-1\" id=\"muD-Qn-j4w\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Correct Spelling Automatically\" id=\"78Y-hA-62v\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticSpellingCorrection:\" target=\"-1\" id=\"2lM-Qi-WAP\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Substitutions\" id=\"9ic-FL-obx\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Substitutions\" id=\"FeM-D8-WVr\">\n                                    <items>\n                                        <menuItem title=\"Show Substitutions\" id=\"z6F-FW-3nz\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"orderFrontSubstitutionsPanel:\" target=\"-1\" id=\"oku-mr-iSq\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem isSeparatorItem=\"YES\" id=\"gPx-C9-uUO\"/>\n                                        <menuItem title=\"Smart Copy/Paste\" id=\"9yt-4B-nSM\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleSmartInsertDelete:\" target=\"-1\" id=\"3IJ-Se-DZD\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Smart Quotes\" id=\"hQb-2v-fYv\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticQuoteSubstitution:\" target=\"-1\" id=\"ptq-xd-QOA\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Smart Dashes\" id=\"rgM-f4-ycn\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticDashSubstitution:\" target=\"-1\" id=\"oCt-pO-9gS\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Smart Links\" id=\"cwL-P1-jid\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticLinkDetection:\" target=\"-1\" id=\"Gip-E3-Fov\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Data Detectors\" id=\"tRr-pd-1PS\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticDataDetection:\" target=\"-1\" id=\"R1I-Nq-Kbl\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Text Replacement\" id=\"HFQ-gK-NFA\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"toggleAutomaticTextReplacement:\" target=\"-1\" id=\"DvP-Fe-Py6\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Transformations\" id=\"2oI-Rn-ZJC\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Transformations\" id=\"c8a-y6-VQd\">\n                                    <items>\n                                        <menuItem title=\"Make Upper Case\" id=\"vmV-6d-7jI\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"uppercaseWord:\" target=\"-1\" id=\"sPh-Tk-edu\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Make Lower Case\" id=\"d9M-CD-aMd\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"lowercaseWord:\" target=\"-1\" id=\"iUZ-b5-hil\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Capitalize\" id=\"UEZ-Bs-lqG\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"capitalizeWord:\" target=\"-1\" id=\"26H-TL-nsh\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                            <menuItem title=\"Speech\" id=\"xrE-MZ-jX0\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <menu key=\"submenu\" title=\"Speech\" id=\"3rS-ZA-NoH\">\n                                    <items>\n                                        <menuItem title=\"Start Speaking\" id=\"Ynk-f8-cLZ\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"startSpeaking:\" target=\"-1\" id=\"654-Ng-kyl\"/>\n                                            </connections>\n                                        </menuItem>\n                                        <menuItem title=\"Stop Speaking\" id=\"Oyz-dy-DGm\">\n                                            <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                            <connections>\n                                                <action selector=\"stopSpeaking:\" target=\"-1\" id=\"dX8-6p-jy9\"/>\n                                            </connections>\n                                        </menuItem>\n                                    </items>\n                                </menu>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"View\" id=\"H8h-7b-M4v\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"View\" id=\"HyV-fh-RgO\">\n                        <items>\n                            <menuItem title=\"Enter Full Screen\" keyEquivalent=\"f\" id=\"4J7-dP-txa\">\n                                <modifierMask key=\"keyEquivalentModifierMask\" control=\"YES\" command=\"YES\"/>\n                                <connections>\n                                    <action selector=\"toggleFullScreen:\" target=\"-1\" id=\"dU3-MA-1Rq\"/>\n                                </connections>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"Window\" id=\"aUF-d1-5bR\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"Window\" systemMenu=\"window\" id=\"Td7-aD-5lo\">\n                        <items>\n                            <menuItem title=\"Minimize\" keyEquivalent=\"m\" id=\"OY7-WF-poV\">\n                                <connections>\n                                    <action selector=\"performMiniaturize:\" target=\"-1\" id=\"VwT-WD-YPe\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem title=\"Zoom\" id=\"R4o-n2-Eq4\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"performZoom:\" target=\"-1\" id=\"DIl-cC-cCs\"/>\n                                </connections>\n                            </menuItem>\n                            <menuItem isSeparatorItem=\"YES\" id=\"eu3-7i-yIM\"/>\n                            <menuItem title=\"Bring All to Front\" id=\"LE2-aR-0XJ\">\n                                <modifierMask key=\"keyEquivalentModifierMask\"/>\n                                <connections>\n                                    <action selector=\"arrangeInFront:\" target=\"-1\" id=\"DRN-fu-gQh\"/>\n                                </connections>\n                            </menuItem>\n                        </items>\n                    </menu>\n                </menuItem>\n                <menuItem title=\"Help\" id=\"EPT-qC-fAb\">\n                    <modifierMask key=\"keyEquivalentModifierMask\"/>\n                    <menu key=\"submenu\" title=\"Help\" systemMenu=\"help\" id=\"rJ0-wn-3NY\"/>\n                </menuItem>\n            </items>\n            <point key=\"canvasLocation\" x=\"142\" y=\"-258\"/>\n        </menu>\n        <window title=\"APP_NAME\" allowsToolTipsWhenApplicationIsInactive=\"NO\" autorecalculatesKeyViewLoop=\"NO\" releasedWhenClosed=\"NO\" animationBehavior=\"default\" id=\"QvC-M9-y7g\" customClass=\"MainFlutterWindow\" customModule=\"Runner\" customModuleProvider=\"target\">\n            <windowStyleMask key=\"styleMask\" titled=\"YES\" closable=\"YES\" miniaturizable=\"YES\" resizable=\"YES\"/>\n            <rect key=\"contentRect\" x=\"335\" y=\"390\" width=\"800\" height=\"600\"/>\n            <rect key=\"screenRect\" x=\"0.0\" y=\"0.0\" width=\"2560\" height=\"1577\"/>\n            <view key=\"contentView\" wantsLayer=\"YES\" id=\"EiT-Mj-1SZ\">\n                <rect key=\"frame\" x=\"0.0\" y=\"0.0\" width=\"800\" height=\"600\"/>\n                <autoresizingMask key=\"autoresizingMask\"/>\n            </view>\n        </window>\n    </objects>\n</document>\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/Configs/AppInfo.xcconfig",
    "content": "// Application-level settings for the Runner target.\n//\n// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the\n// future. If not, the values below would default to using the project name when this becomes a\n// 'flutter create' template.\n\n// The application's name. By default this is also the title of the Flutter window.\nPRODUCT_NAME = bookkeeping_user_flutter\n\n// The application's bundle identifier\nPRODUCT_BUNDLE_IDENTIFIER = com.jiukuaitech.bookkeepingUserFlutter\n\n// The copyright displayed in application information\nPRODUCT_COPYRIGHT = Copyright © 2022 com.jiukuaitech. All rights reserved.\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/Configs/Debug.xcconfig",
    "content": "#include \"../../Flutter/Flutter-Debug.xcconfig\"\n#include \"Warnings.xcconfig\"\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/Configs/Release.xcconfig",
    "content": "#include \"../../Flutter/Flutter-Release.xcconfig\"\n#include \"Warnings.xcconfig\"\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/Configs/Warnings.xcconfig",
    "content": "WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings\nGCC_WARN_UNDECLARED_SELECTOR = YES\nCLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES\nCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE\nCLANG_WARN__DUPLICATE_METHOD_MATCH = YES\nCLANG_WARN_PRAGMA_PACK = YES\nCLANG_WARN_STRICT_PROTOTYPES = YES\nCLANG_WARN_COMMA = YES\nGCC_WARN_STRICT_SELECTOR_MATCH = YES\nCLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES\nCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES\nGCC_WARN_SHADOW = YES\nCLANG_WARN_UNREACHABLE_CODE = YES\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/DebugProfile.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<true/>\n\t<key>com.apple.security.cs.allow-jit</key>\n\t<true/>\n\t<key>com.apple.security.network.server</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIconFile</key>\n\t<string></string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>$(FLUTTER_BUILD_NAME)</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(FLUTTER_BUILD_NUMBER)</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>$(MACOSX_DEPLOYMENT_TARGET)</string>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>$(PRODUCT_COPYRIGHT)</string>\n\t<key>NSMainNibFile</key>\n\t<string>MainMenu</string>\n\t<key>NSPrincipalClass</key>\n\t<string>NSApplication</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/MainFlutterWindow.swift",
    "content": "import Cocoa\nimport FlutterMacOS\n\nclass MainFlutterWindow: NSWindow {\n  override func awakeFromNib() {\n    let flutterViewController = FlutterViewController.init()\n    let windowFrame = self.frame\n    self.contentViewController = flutterViewController\n    self.setFrame(windowFrame, display: true)\n\n    RegisterGeneratedPlugins(registry: flutterViewController)\n\n    super.awakeFromNib()\n  }\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner/Release.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 51;\n\tobjects = {\n\n/* Begin PBXAggregateTarget section */\n\t\t33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {\n\t\t\tisa = PBXAggregateTarget;\n\t\t\tbuildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget \"Flutter Assemble\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t33CC111E2044C6BF0003C045 /* ShellScript */,\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = \"Flutter Assemble\";\n\t\t\tproductName = FLX;\n\t\t};\n/* End PBXAggregateTarget section */\n\n/* Begin PBXBuildFile section */\n\t\t27FF91265827600A1F59F5C9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4019E5EB853DB35F883A3375 /* Pods_Runner.framework */; };\n\t\t335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };\n\t\t33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };\n\t\t33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };\n\t\t33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };\n\t\t33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 33CC10E52044A3C60003C045 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 33CC111A2044C6BA0003C045;\n\t\t\tremoteInfo = FLX;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t33CC110E2044A8840003C045 /* Bundle Framework */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tname = \"Bundle Framework\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = \"<group>\"; };\n\t\t335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = \"<group>\"; };\n\t\t33CC10ED2044A3C60003C045 /* bookkeeping_user_flutter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = bookkeeping_user_flutter.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = \"<group>\"; };\n\t\t33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = \"<group>\"; };\n\t\t33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = \"<group>\"; };\n\t\t33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = \"<group>\"; };\n\t\t33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = \"Flutter-Debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = \"Flutter-Release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = \"Flutter-Generated.xcconfig\"; path = \"ephemeral/Flutter-Generated.xcconfig\"; sourceTree = \"<group>\"; };\n\t\t33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = \"<group>\"; };\n\t\t33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = \"<group>\"; };\n\t\t33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = \"<group>\"; };\n\t\t4019E5EB853DB35F883A3375 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = \"<group>\"; };\n\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = \"<group>\"; };\n\t\tE43660232EF8CA53192A9F75 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.debug.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tEDB9382B6BEDC46ABCFCD6F2 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.release.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig\"; sourceTree = \"<group>\"; };\n\t\tF28E0351EDEB7C94CB824AC0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = \"Pods-Runner.profile.xcconfig\"; path = \"Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig\"; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t33CC10EA2044A3C60003C045 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t27FF91265827600A1F59F5C9 /* Pods_Runner.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t33BA886A226E78AF003329D5 /* Configs */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t33E5194F232828860026EE4D /* AppInfo.xcconfig */,\n\t\t\t\t9740EEB21CF90195004384FC /* Debug.xcconfig */,\n\t\t\t\t7AFA3C8E1D35360C0083082E /* Release.xcconfig */,\n\t\t\t\t333000ED22D3DE5D00554162 /* Warnings.xcconfig */,\n\t\t\t);\n\t\t\tpath = Configs;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t33CC10E42044A3C60003C045 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t33FAB671232836740065AC1E /* Runner */,\n\t\t\t\t33CEB47122A05771004F2AC0 /* Flutter */,\n\t\t\t\t33CC10EE2044A3C60003C045 /* Products */,\n\t\t\t\tD73912EC22F37F3D000D13A0 /* Frameworks */,\n\t\t\t\t6013DB3183603D80E6B01304 /* Pods */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t33CC10EE2044A3C60003C045 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t33CC10ED2044A3C60003C045 /* bookkeeping_user_flutter.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t33CC11242044D66E0003C045 /* Resources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t33CC10F22044A3C60003C045 /* Assets.xcassets */,\n\t\t\t\t33CC10F42044A3C60003C045 /* MainMenu.xib */,\n\t\t\t\t33CC10F72044A3C60003C045 /* Info.plist */,\n\t\t\t);\n\t\t\tname = Resources;\n\t\t\tpath = ..;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t33CEB47122A05771004F2AC0 /* Flutter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,\n\t\t\t\t33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,\n\t\t\t\t33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,\n\t\t\t\t33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,\n\t\t\t);\n\t\t\tpath = Flutter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t33FAB671232836740065AC1E /* Runner */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t33CC10F02044A3C60003C045 /* AppDelegate.swift */,\n\t\t\t\t33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,\n\t\t\t\t33E51913231747F40026EE4D /* DebugProfile.entitlements */,\n\t\t\t\t33E51914231749380026EE4D /* Release.entitlements */,\n\t\t\t\t33CC11242044D66E0003C045 /* Resources */,\n\t\t\t\t33BA886A226E78AF003329D5 /* Configs */,\n\t\t\t);\n\t\t\tpath = Runner;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6013DB3183603D80E6B01304 /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tE43660232EF8CA53192A9F75 /* Pods-Runner.debug.xcconfig */,\n\t\t\t\tEDB9382B6BEDC46ABCFCD6F2 /* Pods-Runner.release.xcconfig */,\n\t\t\t\tF28E0351EDEB7C94CB824AC0 /* Pods-Runner.profile.xcconfig */,\n\t\t\t);\n\t\t\tname = Pods;\n\t\t\tpath = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tD73912EC22F37F3D000D13A0 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t4019E5EB853DB35F883A3375 /* Pods_Runner.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t33CC10EC2044A3C60003C045 /* Runner */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget \"Runner\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tA4CD32F08EE403CC072F2229 /* [CP] Check Pods Manifest.lock */,\n\t\t\t\t33CC10E92044A3C60003C045 /* Sources */,\n\t\t\t\t33CC10EA2044A3C60003C045 /* Frameworks */,\n\t\t\t\t33CC10EB2044A3C60003C045 /* Resources */,\n\t\t\t\t33CC110E2044A8840003C045 /* Bundle Framework */,\n\t\t\t\t3399D490228B24CF009A79C7 /* ShellScript */,\n\t\t\t\t0AF52EB48178C9F30463C578 /* [CP] Embed Pods Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t33CC11202044C79F0003C045 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = Runner;\n\t\t\tproductName = Runner;\n\t\t\tproductReference = 33CC10ED2044A3C60003C045 /* bookkeeping_user_flutter.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t33CC10E52044A3C60003C045 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastSwiftUpdateCheck = 0920;\n\t\t\t\tLastUpgradeCheck = 1300;\n\t\t\t\tORGANIZATIONNAME = \"\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t33CC10EC2044A3C60003C045 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 9.2;\n\t\t\t\t\t\tLastSwiftMigration = 1100;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t\tSystemCapabilities = {\n\t\t\t\t\t\t\tcom.apple.Sandbox = {\n\t\t\t\t\t\t\t\tenabled = 1;\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\t33CC111A2044C6BA0003C045 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 9.2;\n\t\t\t\t\t\tProvisioningStyle = Manual;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject \"Runner\" */;\n\t\t\tcompatibilityVersion = \"Xcode 9.3\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 33CC10E42044A3C60003C045;\n\t\t\tproductRefGroup = 33CC10EE2044A3C60003C045 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t33CC10EC2044A3C60003C045 /* Runner */,\n\t\t\t\t33CC111A2044C6BA0003C045 /* Flutter Assemble */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t33CC10EB2044A3C60003C045 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,\n\t\t\t\t33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXShellScriptBuildPhase section */\n\t\t0AF52EB48178C9F30463C578 /* [CP] Embed Pods Frameworks */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist\",\n\t\t\t);\n\t\t\tname = \"[CP] Embed Pods Frameworks\";\n\t\t\toutputFileListPaths = (\n\t\t\t\t\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n\t\t3399D490228B24CF009A79C7 /* ShellScript */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t);\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"echo \\\"$PRODUCT_NAME.app\\\" > \\\"$PROJECT_DIR\\\"/Flutter/ephemeral/.app_filename && \\\"$FLUTTER_ROOT\\\"/packages/flutter_tools/bin/macos_assemble.sh embed\\n\";\n\t\t};\n\t\t33CC111E2044C6BF0003C045 /* ShellScript */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t\tFlutter/ephemeral/FlutterInputs.xcfilelist,\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\tFlutter/ephemeral/tripwire,\n\t\t\t);\n\t\t\toutputFileListPaths = (\n\t\t\t\tFlutter/ephemeral/FlutterOutputs.xcfilelist,\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"\\\"$FLUTTER_ROOT\\\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire\";\n\t\t};\n\t\tA4CD32F08EE403CC072F2229 /* [CP] Check Pods Manifest.lock */ = {\n\t\t\tisa = PBXShellScriptBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tinputFileListPaths = (\n\t\t\t);\n\t\t\tinputPaths = (\n\t\t\t\t\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\",\n\t\t\t\t\"${PODS_ROOT}/Manifest.lock\",\n\t\t\t);\n\t\t\tname = \"[CP] Check Pods Manifest.lock\";\n\t\t\toutputFileListPaths = (\n\t\t\t);\n\t\t\toutputPaths = (\n\t\t\t\t\"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt\",\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t\tshellPath = /bin/sh;\n\t\t\tshellScript = \"diff \\\"${PODS_PODFILE_DIR_PATH}/Podfile.lock\\\" \\\"${PODS_ROOT}/Manifest.lock\\\" > /dev/null\\nif [ $? != 0 ] ; then\\n    # print error to STDERR\\n    echo \\\"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\\\" >&2\\n    exit 1\\nfi\\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\\necho \\\"SUCCESS\\\" > \\\"${SCRIPT_OUTPUT_FILE_0}\\\"\\n\";\n\t\t\tshowEnvVarsInLog = 0;\n\t\t};\n/* End PBXShellScriptBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t33CC10E92044A3C60003C045 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,\n\t\t\t\t33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,\n\t\t\t\t335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t33CC11202044C79F0003C045 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;\n\t\t\ttargetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin PBXVariantGroup section */\n\t\t33CC10F42044A3C60003C045 /* MainMenu.xib */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t33CC10F52044A3C60003C045 /* Base */,\n\t\t\t);\n\t\t\tname = MainMenu.xib;\n\t\t\tpath = Runner;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t338D0CE9231458BD00FA5F75 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.11;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t338D0CEA231458BD00FA5F75 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t338D0CEB231458BD00FA5F75 /* Profile */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Profile;\n\t\t};\n\t\t33CC10F92044A3C60003C045 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.11;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t33CC10FA2044A3C60003C045 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCODE_SIGN_IDENTITY = \"-\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 10.11;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t33CC10FC2044A3C60003C045 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t33CC10FD2044A3C60003C045 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tINFOPLIST_FILE = Runner/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t33CC111C2044C6BA0003C045 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t33CC111D2044C6BA0003C045 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t33CC10E82044A3C60003C045 /* Build configuration list for PBXProject \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t33CC10F92044A3C60003C045 /* Debug */,\n\t\t\t\t33CC10FA2044A3C60003C045 /* Release */,\n\t\t\t\t338D0CE9231458BD00FA5F75 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget \"Runner\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t33CC10FC2044A3C60003C045 /* Debug */,\n\t\t\t\t33CC10FD2044A3C60003C045 /* Release */,\n\t\t\t\t338D0CEA231458BD00FA5F75 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget \"Flutter Assemble\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t33CC111C2044C6BA0003C045 /* Debug */,\n\t\t\t\t33CC111D2044C6BA0003C045 /* Release */,\n\t\t\t\t338D0CEB231458BD00FA5F75 /* Profile */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 33CC10E52044A3C60003C045 /* Project object */;\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1300\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"33CC10EC2044A3C60003C045\"\n               BuildableName = \"bookkeeping_user_flutter.app\"\n               BlueprintName = \"Runner\"\n               ReferencedContainer = \"container:Runner.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"33CC10EC2044A3C60003C045\"\n            BuildableName = \"bookkeeping_user_flutter.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n      <Testables>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"33CC10EC2044A3C60003C045\"\n            BuildableName = \"bookkeeping_user_flutter.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Profile\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <BuildableProductRunnable\n         runnableDebuggingMode = \"0\">\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"33CC10EC2044A3C60003C045\"\n            BuildableName = \"bookkeeping_user_flutter.app\"\n            BlueprintName = \"Runner\"\n            ReferencedContainer = \"container:Runner.xcodeproj\">\n         </BuildableReference>\n      </BuildableProductRunnable>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Runner.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:Pods/Pods.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "bookkeeping_user_flutter/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "bookkeeping_user_flutter/pubspec.yaml",
    "content": "name: bookkeeping_user_flutter\ndescription: A new Flutter project.\npublish_to: 'none'\nversion: 1.0.0+1\nenvironment:\n  sdk: \">=2.14.4 <3.0.0\"\ndependencies:\n  flutter:\n    sdk: flutter\n  intl: ^0.17.0 #日期格式化\n  dio: ^4.0.6\n  formz: ^0.4.1\n  shared_preferences: ^2.0.15\n  equatable: ^2.0.5\n  flutter_bloc: ^8.1.1\n  json_annotation: ^4.7.0\n  awesome_select: ^6.0.0\n  pull_to_refresh: ^2.0.0\n  fluttertoast: ^8.1.1\n  syncfusion_flutter_charts: ^20.3.52\n  syncfusion_flutter_datepicker: ^20.3.52\n  image_picker: ^0.8.6\n  flutter_localizations:\n    sdk: flutter\n  webview_flutter: ^3.0.4\ndev_dependencies:\n  flutter_test:\n    sdk: flutter\n  build_runner: ^2.3.2\n  json_serializable: ^6.5.4\nflutter:\n  uses-material-design: true\n  assets:\n    - assets/images/\n  generate: false"
  },
  {
    "path": "bookkeeping_user_flutter/test/widget_test.dart",
    "content": "// This is a basic Flutter widget test.\n//\n// To perform an interaction with a widget in your test, use the WidgetTester\n// utility in the flutter_test package. For example, you can send tap and scroll\n// gestures. You can also use WidgetTester to find child widgets in the widget\n// tree, read text, and verify that the values of widget properties are correct.\n\nimport 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\n\nimport 'package:bookkeeping_user_flutter/main.dart';\n\nvoid main() {\n  testWidgets('Counter increments smoke test', (WidgetTester tester) async {\n    // Build our app and trigger a frame.\n    // await tester.pumpWidget(const MyApp());\n\n    // Verify that our counter starts at 0.\n    expect(find.text('0'), findsOneWidget);\n    expect(find.text('1'), findsNothing);\n\n    // Tap the '+' icon and trigger a frame.\n    await tester.tap(find.byIcon(Icons.add));\n    await tester.pump();\n\n    // Verify that our counter has incremented.\n    expect(find.text('0'), findsNothing);\n    expect(find.text('1'), findsOneWidget);\n  });\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/.gitignore",
    "content": "flutter/ephemeral/\n\n# Visual Studio user-specific files.\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# Visual Studio build-related files.\nx64/\nx86/\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!*.[Cc]ache/\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/CMakeLists.txt",
    "content": "# Project-level configuration.\ncmake_minimum_required(VERSION 3.14)\nproject(bookkeeping_user_flutter LANGUAGES CXX)\n\n# The name of the executable created for the application. Change this to change\n# the on-disk name of your application.\nset(BINARY_NAME \"bookkeeping_user_flutter\")\n\n# Explicitly opt in to modern CMake behaviors to avoid warnings with recent\n# versions of CMake.\ncmake_policy(SET CMP0063 NEW)\n\n# Define build configuration option.\nget_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)\nif(IS_MULTICONFIG)\n  set(CMAKE_CONFIGURATION_TYPES \"Debug;Profile;Release\"\n    CACHE STRING \"\" FORCE)\nelse()\n  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)\n    set(CMAKE_BUILD_TYPE \"Debug\" CACHE\n      STRING \"Flutter build mode\" FORCE)\n    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS\n      \"Debug\" \"Profile\" \"Release\")\n  endif()\nendif()\n# Define settings for the Profile build mode.\nset(CMAKE_EXE_LINKER_FLAGS_PROFILE \"${CMAKE_EXE_LINKER_FLAGS_RELEASE}\")\nset(CMAKE_SHARED_LINKER_FLAGS_PROFILE \"${CMAKE_SHARED_LINKER_FLAGS_RELEASE}\")\nset(CMAKE_C_FLAGS_PROFILE \"${CMAKE_C_FLAGS_RELEASE}\")\nset(CMAKE_CXX_FLAGS_PROFILE \"${CMAKE_CXX_FLAGS_RELEASE}\")\n\n# Use Unicode for all projects.\nadd_definitions(-DUNICODE -D_UNICODE)\n\n# Compilation settings that should be applied to most targets.\n#\n# Be cautious about adding new options here, as plugins use this function by\n# default. In most cases, you should add new options to specific targets instead\n# of modifying this function.\nfunction(APPLY_STANDARD_SETTINGS TARGET)\n  target_compile_features(${TARGET} PUBLIC cxx_std_17)\n  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd\"4100\")\n  target_compile_options(${TARGET} PRIVATE /EHsc)\n  target_compile_definitions(${TARGET} PRIVATE \"_HAS_EXCEPTIONS=0\")\n  target_compile_definitions(${TARGET} PRIVATE \"$<$<CONFIG:Debug>:_DEBUG>\")\nendfunction()\n\n# Flutter library and tool build rules.\nset(FLUTTER_MANAGED_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/flutter\")\nadd_subdirectory(${FLUTTER_MANAGED_DIR})\n\n# Application build; see runner/CMakeLists.txt.\nadd_subdirectory(\"runner\")\n\n# Generated plugin build rules, which manage building the plugins and adding\n# them to the application.\ninclude(flutter/generated_plugins.cmake)\n\n\n# === Installation ===\n# Support files are copied into place next to the executable, so that it can\n# run in place. This is done instead of making a separate bundle (as on Linux)\n# so that building and running from within Visual Studio will work.\nset(BUILD_BUNDLE_DIR \"$<TARGET_FILE_DIR:${BINARY_NAME}>\")\n# Make the \"install\" step default, as it's required to run.\nset(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)\nif(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)\n  set(CMAKE_INSTALL_PREFIX \"${BUILD_BUNDLE_DIR}\" CACHE PATH \"...\" FORCE)\nendif()\n\nset(INSTALL_BUNDLE_DATA_DIR \"${CMAKE_INSTALL_PREFIX}/data\")\nset(INSTALL_BUNDLE_LIB_DIR \"${CMAKE_INSTALL_PREFIX}\")\n\ninstall(TARGETS ${BINARY_NAME} RUNTIME DESTINATION \"${CMAKE_INSTALL_PREFIX}\"\n  COMPONENT Runtime)\n\ninstall(FILES \"${FLUTTER_ICU_DATA_FILE}\" DESTINATION \"${INSTALL_BUNDLE_DATA_DIR}\"\n  COMPONENT Runtime)\n\ninstall(FILES \"${FLUTTER_LIBRARY}\" DESTINATION \"${INSTALL_BUNDLE_LIB_DIR}\"\n  COMPONENT Runtime)\n\nif(PLUGIN_BUNDLED_LIBRARIES)\n  install(FILES \"${PLUGIN_BUNDLED_LIBRARIES}\"\n    DESTINATION \"${INSTALL_BUNDLE_LIB_DIR}\"\n    COMPONENT Runtime)\nendif()\n\n# Fully re-copy the assets directory on each build to avoid having stale files\n# from a previous install.\nset(FLUTTER_ASSET_DIR_NAME \"flutter_assets\")\ninstall(CODE \"\n  file(REMOVE_RECURSE \\\"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\\\")\n  \" COMPONENT Runtime)\ninstall(DIRECTORY \"${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}\"\n  DESTINATION \"${INSTALL_BUNDLE_DATA_DIR}\" COMPONENT Runtime)\n\n# Install the AOT library on non-Debug builds only.\ninstall(FILES \"${AOT_LIBRARY}\" DESTINATION \"${INSTALL_BUNDLE_DATA_DIR}\"\n  CONFIGURATIONS Profile;Release\n  COMPONENT Runtime)\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/flutter/CMakeLists.txt",
    "content": "# This file controls Flutter-level build steps. It should not be edited.\ncmake_minimum_required(VERSION 3.14)\n\nset(EPHEMERAL_DIR \"${CMAKE_CURRENT_SOURCE_DIR}/ephemeral\")\n\n# Configuration provided via flutter tool.\ninclude(${EPHEMERAL_DIR}/generated_config.cmake)\n\n# TODO: Move the rest of this into files in ephemeral. See\n# https://github.com/flutter/flutter/issues/57146.\nset(WRAPPER_ROOT \"${EPHEMERAL_DIR}/cpp_client_wrapper\")\n\n# === Flutter Library ===\nset(FLUTTER_LIBRARY \"${EPHEMERAL_DIR}/flutter_windows.dll\")\n\n# Published to parent scope for install step.\nset(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)\nset(FLUTTER_ICU_DATA_FILE \"${EPHEMERAL_DIR}/icudtl.dat\" PARENT_SCOPE)\nset(PROJECT_BUILD_DIR \"${PROJECT_DIR}/build/\" PARENT_SCOPE)\nset(AOT_LIBRARY \"${PROJECT_DIR}/build/windows/app.so\" PARENT_SCOPE)\n\nlist(APPEND FLUTTER_LIBRARY_HEADERS\n  \"flutter_export.h\"\n  \"flutter_windows.h\"\n  \"flutter_messenger.h\"\n  \"flutter_plugin_registrar.h\"\n  \"flutter_texture_registrar.h\"\n)\nlist(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND \"${EPHEMERAL_DIR}/\")\nadd_library(flutter INTERFACE)\ntarget_include_directories(flutter INTERFACE\n  \"${EPHEMERAL_DIR}\"\n)\ntarget_link_libraries(flutter INTERFACE \"${FLUTTER_LIBRARY}.lib\")\nadd_dependencies(flutter flutter_assemble)\n\n# === Wrapper ===\nlist(APPEND CPP_WRAPPER_SOURCES_CORE\n  \"core_implementations.cc\"\n  \"standard_codec.cc\"\n)\nlist(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND \"${WRAPPER_ROOT}/\")\nlist(APPEND CPP_WRAPPER_SOURCES_PLUGIN\n  \"plugin_registrar.cc\"\n)\nlist(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND \"${WRAPPER_ROOT}/\")\nlist(APPEND CPP_WRAPPER_SOURCES_APP\n  \"flutter_engine.cc\"\n  \"flutter_view_controller.cc\"\n)\nlist(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND \"${WRAPPER_ROOT}/\")\n\n# Wrapper sources needed for a plugin.\nadd_library(flutter_wrapper_plugin STATIC\n  ${CPP_WRAPPER_SOURCES_CORE}\n  ${CPP_WRAPPER_SOURCES_PLUGIN}\n)\napply_standard_settings(flutter_wrapper_plugin)\nset_target_properties(flutter_wrapper_plugin PROPERTIES\n  POSITION_INDEPENDENT_CODE ON)\nset_target_properties(flutter_wrapper_plugin PROPERTIES\n  CXX_VISIBILITY_PRESET hidden)\ntarget_link_libraries(flutter_wrapper_plugin PUBLIC flutter)\ntarget_include_directories(flutter_wrapper_plugin PUBLIC\n  \"${WRAPPER_ROOT}/include\"\n)\nadd_dependencies(flutter_wrapper_plugin flutter_assemble)\n\n# Wrapper sources needed for the runner.\nadd_library(flutter_wrapper_app STATIC\n  ${CPP_WRAPPER_SOURCES_CORE}\n  ${CPP_WRAPPER_SOURCES_APP}\n)\napply_standard_settings(flutter_wrapper_app)\ntarget_link_libraries(flutter_wrapper_app PUBLIC flutter)\ntarget_include_directories(flutter_wrapper_app PUBLIC\n  \"${WRAPPER_ROOT}/include\"\n)\nadd_dependencies(flutter_wrapper_app flutter_assemble)\n\n# === Flutter tool backend ===\n# _phony_ is a non-existent file to force this command to run every time,\n# since currently there's no way to get a full input/output list from the\n# flutter tool.\nset(PHONY_OUTPUT \"${CMAKE_CURRENT_BINARY_DIR}/_phony_\")\nset_source_files_properties(\"${PHONY_OUTPUT}\" PROPERTIES SYMBOLIC TRUE)\nadd_custom_command(\n  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}\n    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}\n    ${CPP_WRAPPER_SOURCES_APP}\n    ${PHONY_OUTPUT}\n  COMMAND ${CMAKE_COMMAND} -E env\n    ${FLUTTER_TOOL_ENVIRONMENT}\n    \"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat\"\n      windows-x64 $<CONFIG>\n  VERBATIM\n)\nadd_custom_target(flutter_assemble DEPENDS\n  \"${FLUTTER_LIBRARY}\"\n  ${FLUTTER_LIBRARY_HEADERS}\n  ${CPP_WRAPPER_SOURCES_CORE}\n  ${CPP_WRAPPER_SOURCES_PLUGIN}\n  ${CPP_WRAPPER_SOURCES_APP}\n)\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/flutter/generated_plugin_registrant.cc",
    "content": "//\n//  Generated file. Do not edit.\n//\n\n// clang-format off\n\n#include \"generated_plugin_registrant.h\"\n\n\nvoid RegisterPlugins(flutter::PluginRegistry* registry) {\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/flutter/generated_plugin_registrant.h",
    "content": "//\n//  Generated file. Do not edit.\n//\n\n// clang-format off\n\n#ifndef GENERATED_PLUGIN_REGISTRANT_\n#define GENERATED_PLUGIN_REGISTRANT_\n\n#include <flutter/plugin_registry.h>\n\n// Registers Flutter plugins.\nvoid RegisterPlugins(flutter::PluginRegistry* registry);\n\n#endif  // GENERATED_PLUGIN_REGISTRANT_\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/flutter/generated_plugins.cmake",
    "content": "#\n# Generated file, do not edit.\n#\n\nlist(APPEND FLUTTER_PLUGIN_LIST\n)\n\nlist(APPEND FLUTTER_FFI_PLUGIN_LIST\n)\n\nset(PLUGIN_BUNDLED_LIBRARIES)\n\nforeach(plugin ${FLUTTER_PLUGIN_LIST})\n  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})\n  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})\nendforeach(plugin)\n\nforeach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})\n  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})\n  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})\nendforeach(ffi_plugin)\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.14)\nproject(runner LANGUAGES CXX)\n\n# Define the application target. To change its name, change BINARY_NAME in the\n# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer\n# work.\n#\n# Any new source files that you add to the application should be added here.\nadd_executable(${BINARY_NAME} WIN32\n  \"flutter_window.cpp\"\n  \"main.cpp\"\n  \"utils.cpp\"\n  \"win32_window.cpp\"\n  \"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc\"\n  \"Runner.rc\"\n  \"runner.exe.manifest\"\n)\n\n# Apply the standard set of build settings. This can be removed for applications\n# that need different build settings.\napply_standard_settings(${BINARY_NAME})\n\n# Disable Windows macros that collide with C++ standard library functions.\ntarget_compile_definitions(${BINARY_NAME} PRIVATE \"NOMINMAX\")\n\n# Add dependency libraries and include directories. Add any application-specific\n# dependencies here.\ntarget_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)\ntarget_include_directories(${BINARY_NAME} PRIVATE \"${CMAKE_SOURCE_DIR}\")\n\n# Run the Flutter tool portions of the build. This must not be removed.\nadd_dependencies(${BINARY_NAME} flutter_assemble)\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/Runner.rc",
    "content": "// Microsoft Visual C++ generated resource script.\n//\n#pragma code_page(65001)\n#include \"resource.h\"\n\n#define APSTUDIO_READONLY_SYMBOLS\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 2 resource.\n//\n#include \"winres.h\"\n\n/////////////////////////////////////////////////////////////////////////////\n#undef APSTUDIO_READONLY_SYMBOLS\n\n/////////////////////////////////////////////////////////////////////////////\n// English (United States) resources\n\n#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)\nLANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US\n\n#ifdef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// TEXTINCLUDE\n//\n\n1 TEXTINCLUDE\nBEGIN\n    \"resource.h\\0\"\nEND\n\n2 TEXTINCLUDE\nBEGIN\n    \"#include \"\"winres.h\"\"\\r\\n\"\n    \"\\0\"\nEND\n\n3 TEXTINCLUDE\nBEGIN\n    \"\\r\\n\"\n    \"\\0\"\nEND\n\n#endif    // APSTUDIO_INVOKED\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Icon\n//\n\n// Icon with lowest ID value placed first to ensure application icon\n// remains consistent on all systems.\nIDI_APP_ICON            ICON                    \"resources\\\\app_icon.ico\"\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Version\n//\n\n#ifdef FLUTTER_BUILD_NUMBER\n#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER\n#else\n#define VERSION_AS_NUMBER 1,0,0\n#endif\n\n#ifdef FLUTTER_BUILD_NAME\n#define VERSION_AS_STRING #FLUTTER_BUILD_NAME\n#else\n#define VERSION_AS_STRING \"1.0.0\"\n#endif\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION VERSION_AS_NUMBER\n PRODUCTVERSION VERSION_AS_NUMBER\n FILEFLAGSMASK VS_FFI_FILEFLAGSMASK\n#ifdef _DEBUG\n FILEFLAGS VS_FF_DEBUG\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS VOS__WINDOWS32\n FILETYPE VFT_APP\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"040904e4\"\n        BEGIN\n            VALUE \"CompanyName\", \"com.jiukuaitech\" \"\\0\"\n            VALUE \"FileDescription\", \"bookkeeping_user_flutter\" \"\\0\"\n            VALUE \"FileVersion\", VERSION_AS_STRING \"\\0\"\n            VALUE \"InternalName\", \"bookkeeping_user_flutter\" \"\\0\"\n            VALUE \"LegalCopyright\", \"Copyright (C) 2022 com.jiukuaitech. All rights reserved.\" \"\\0\"\n            VALUE \"OriginalFilename\", \"bookkeeping_user_flutter.exe\" \"\\0\"\n            VALUE \"ProductName\", \"bookkeeping_user_flutter\" \"\\0\"\n            VALUE \"ProductVersion\", VERSION_AS_STRING \"\\0\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x409, 1252\n    END\nEND\n\n#endif    // English (United States) resources\n/////////////////////////////////////////////////////////////////////////////\n\n\n\n#ifndef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 3 resource.\n//\n\n\n/////////////////////////////////////////////////////////////////////////////\n#endif    // not APSTUDIO_INVOKED\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/flutter_window.cpp",
    "content": "#include \"flutter_window.h\"\n\n#include <optional>\n\n#include \"flutter/generated_plugin_registrant.h\"\n\nFlutterWindow::FlutterWindow(const flutter::DartProject& project)\n    : project_(project) {}\n\nFlutterWindow::~FlutterWindow() {}\n\nbool FlutterWindow::OnCreate() {\n  if (!Win32Window::OnCreate()) {\n    return false;\n  }\n\n  RECT frame = GetClientArea();\n\n  // The size here must match the window dimensions to avoid unnecessary surface\n  // creation / destruction in the startup path.\n  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(\n      frame.right - frame.left, frame.bottom - frame.top, project_);\n  // Ensure that basic setup of the controller was successful.\n  if (!flutter_controller_->engine() || !flutter_controller_->view()) {\n    return false;\n  }\n  RegisterPlugins(flutter_controller_->engine());\n  SetChildContent(flutter_controller_->view()->GetNativeWindow());\n  return true;\n}\n\nvoid FlutterWindow::OnDestroy() {\n  if (flutter_controller_) {\n    flutter_controller_ = nullptr;\n  }\n\n  Win32Window::OnDestroy();\n}\n\nLRESULT\nFlutterWindow::MessageHandler(HWND hwnd, UINT const message,\n                              WPARAM const wparam,\n                              LPARAM const lparam) noexcept {\n  // Give Flutter, including plugins, an opportunity to handle window messages.\n  if (flutter_controller_) {\n    std::optional<LRESULT> result =\n        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,\n                                                      lparam);\n    if (result) {\n      return *result;\n    }\n  }\n\n  switch (message) {\n    case WM_FONTCHANGE:\n      flutter_controller_->engine()->ReloadSystemFonts();\n      break;\n  }\n\n  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/flutter_window.h",
    "content": "#ifndef RUNNER_FLUTTER_WINDOW_H_\n#define RUNNER_FLUTTER_WINDOW_H_\n\n#include <flutter/dart_project.h>\n#include <flutter/flutter_view_controller.h>\n\n#include <memory>\n\n#include \"win32_window.h\"\n\n// A window that does nothing but host a Flutter view.\nclass FlutterWindow : public Win32Window {\n public:\n  // Creates a new FlutterWindow hosting a Flutter view running |project|.\n  explicit FlutterWindow(const flutter::DartProject& project);\n  virtual ~FlutterWindow();\n\n protected:\n  // Win32Window:\n  bool OnCreate() override;\n  void OnDestroy() override;\n  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,\n                         LPARAM const lparam) noexcept override;\n\n private:\n  // The project to run.\n  flutter::DartProject project_;\n\n  // The Flutter instance hosted by this window.\n  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;\n};\n\n#endif  // RUNNER_FLUTTER_WINDOW_H_\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/main.cpp",
    "content": "#include <flutter/dart_project.h>\n#include <flutter/flutter_view_controller.h>\n#include <windows.h>\n\n#include \"flutter_window.h\"\n#include \"utils.h\"\n\nint APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,\n                      _In_ wchar_t *command_line, _In_ int show_command) {\n  // Attach to console when present (e.g., 'flutter run') or create a\n  // new console when running with a debugger.\n  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {\n    CreateAndAttachConsole();\n  }\n\n  // Initialize COM, so that it is available for use in the library and/or\n  // plugins.\n  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);\n\n  flutter::DartProject project(L\"data\");\n\n  std::vector<std::string> command_line_arguments =\n      GetCommandLineArguments();\n\n  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));\n\n  FlutterWindow window(project);\n  Win32Window::Point origin(10, 10);\n  Win32Window::Size size(1280, 720);\n  if (!window.CreateAndShow(L\"bookkeeping_user_flutter\", origin, size)) {\n    return EXIT_FAILURE;\n  }\n  window.SetQuitOnClose(true);\n\n  ::MSG msg;\n  while (::GetMessage(&msg, nullptr, 0, 0)) {\n    ::TranslateMessage(&msg);\n    ::DispatchMessage(&msg);\n  }\n\n  ::CoUninitialize();\n  return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\n// Microsoft Visual C++ generated include file.\n// Used by Runner.rc\n//\n#define IDI_APP_ICON                    101\n\n// Next default values for new objects\n//\n#ifdef APSTUDIO_INVOKED\n#ifndef APSTUDIO_READONLY_SYMBOLS\n#define _APS_NEXT_RESOURCE_VALUE        102\n#define _APS_NEXT_COMMAND_VALUE         40001\n#define _APS_NEXT_CONTROL_VALUE         1001\n#define _APS_NEXT_SYMED_VALUE           101\n#endif\n#endif\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/runner.exe.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\n  <application xmlns=\"urn:schemas-microsoft-com:asm.v3\">\n    <windowsSettings>\n      <dpiAwareness xmlns=\"http://schemas.microsoft.com/SMI/2016/WindowsSettings\">PerMonitorV2</dpiAwareness>\n    </windowsSettings>\n  </application>\n  <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n    <application>\n      <!-- Windows 10 -->\n      <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\"/>\n      <!-- Windows 8.1 -->\n      <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\"/>\n      <!-- Windows 8 -->\n      <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\"/>\n      <!-- Windows 7 -->\n      <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/>\n    </application>\n  </compatibility>\n</assembly>\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/utils.cpp",
    "content": "#include \"utils.h\"\n\n#include <flutter_windows.h>\n#include <io.h>\n#include <stdio.h>\n#include <windows.h>\n\n#include <iostream>\n\nvoid CreateAndAttachConsole() {\n  if (::AllocConsole()) {\n    FILE *unused;\n    if (freopen_s(&unused, \"CONOUT$\", \"w\", stdout)) {\n      _dup2(_fileno(stdout), 1);\n    }\n    if (freopen_s(&unused, \"CONOUT$\", \"w\", stderr)) {\n      _dup2(_fileno(stdout), 2);\n    }\n    std::ios::sync_with_stdio();\n    FlutterDesktopResyncOutputStreams();\n  }\n}\n\nstd::vector<std::string> GetCommandLineArguments() {\n  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.\n  int argc;\n  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);\n  if (argv == nullptr) {\n    return std::vector<std::string>();\n  }\n\n  std::vector<std::string> command_line_arguments;\n\n  // Skip the first argument as it's the binary name.\n  for (int i = 1; i < argc; i++) {\n    command_line_arguments.push_back(Utf8FromUtf16(argv[i]));\n  }\n\n  ::LocalFree(argv);\n\n  return command_line_arguments;\n}\n\nstd::string Utf8FromUtf16(const wchar_t* utf16_string) {\n  if (utf16_string == nullptr) {\n    return std::string();\n  }\n  int target_length = ::WideCharToMultiByte(\n      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,\n      -1, nullptr, 0, nullptr, nullptr);\n  std::string utf8_string;\n  if (target_length == 0 || target_length > utf8_string.max_size()) {\n    return utf8_string;\n  }\n  utf8_string.resize(target_length);\n  int converted_length = ::WideCharToMultiByte(\n      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string,\n      -1, utf8_string.data(),\n      target_length, nullptr, nullptr);\n  if (converted_length == 0) {\n    return std::string();\n  }\n  return utf8_string;\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/utils.h",
    "content": "#ifndef RUNNER_UTILS_H_\n#define RUNNER_UTILS_H_\n\n#include <string>\n#include <vector>\n\n// Creates a console for the process, and redirects stdout and stderr to\n// it for both the runner and the Flutter library.\nvoid CreateAndAttachConsole();\n\n// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string\n// encoded in UTF-8. Returns an empty std::string on failure.\nstd::string Utf8FromUtf16(const wchar_t* utf16_string);\n\n// Gets the command line arguments passed in as a std::vector<std::string>,\n// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.\nstd::vector<std::string> GetCommandLineArguments();\n\n#endif  // RUNNER_UTILS_H_\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/win32_window.cpp",
    "content": "#include \"win32_window.h\"\n\n#include <flutter_windows.h>\n\n#include \"resource.h\"\n\nnamespace {\n\nconstexpr const wchar_t kWindowClassName[] = L\"FLUTTER_RUNNER_WIN32_WINDOW\";\n\n// The number of Win32Window objects that currently exist.\nstatic int g_active_window_count = 0;\n\nusing EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);\n\n// Scale helper to convert logical scaler values to physical using passed in\n// scale factor\nint Scale(int source, double scale_factor) {\n  return static_cast<int>(source * scale_factor);\n}\n\n// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.\n// This API is only needed for PerMonitor V1 awareness mode.\nvoid EnableFullDpiSupportIfAvailable(HWND hwnd) {\n  HMODULE user32_module = LoadLibraryA(\"User32.dll\");\n  if (!user32_module) {\n    return;\n  }\n  auto enable_non_client_dpi_scaling =\n      reinterpret_cast<EnableNonClientDpiScaling*>(\n          GetProcAddress(user32_module, \"EnableNonClientDpiScaling\"));\n  if (enable_non_client_dpi_scaling != nullptr) {\n    enable_non_client_dpi_scaling(hwnd);\n    FreeLibrary(user32_module);\n  }\n}\n\n}  // namespace\n\n// Manages the Win32Window's window class registration.\nclass WindowClassRegistrar {\n public:\n  ~WindowClassRegistrar() = default;\n\n  // Returns the singleton registar instance.\n  static WindowClassRegistrar* GetInstance() {\n    if (!instance_) {\n      instance_ = new WindowClassRegistrar();\n    }\n    return instance_;\n  }\n\n  // Returns the name of the window class, registering the class if it hasn't\n  // previously been registered.\n  const wchar_t* GetWindowClass();\n\n  // Unregisters the window class. Should only be called if there are no\n  // instances of the window.\n  void UnregisterWindowClass();\n\n private:\n  WindowClassRegistrar() = default;\n\n  static WindowClassRegistrar* instance_;\n\n  bool class_registered_ = false;\n};\n\nWindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;\n\nconst wchar_t* WindowClassRegistrar::GetWindowClass() {\n  if (!class_registered_) {\n    WNDCLASS window_class{};\n    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);\n    window_class.lpszClassName = kWindowClassName;\n    window_class.style = CS_HREDRAW | CS_VREDRAW;\n    window_class.cbClsExtra = 0;\n    window_class.cbWndExtra = 0;\n    window_class.hInstance = GetModuleHandle(nullptr);\n    window_class.hIcon =\n        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));\n    window_class.hbrBackground = 0;\n    window_class.lpszMenuName = nullptr;\n    window_class.lpfnWndProc = Win32Window::WndProc;\n    RegisterClass(&window_class);\n    class_registered_ = true;\n  }\n  return kWindowClassName;\n}\n\nvoid WindowClassRegistrar::UnregisterWindowClass() {\n  UnregisterClass(kWindowClassName, nullptr);\n  class_registered_ = false;\n}\n\nWin32Window::Win32Window() {\n  ++g_active_window_count;\n}\n\nWin32Window::~Win32Window() {\n  --g_active_window_count;\n  Destroy();\n}\n\nbool Win32Window::CreateAndShow(const std::wstring& title,\n                                const Point& origin,\n                                const Size& size) {\n  Destroy();\n\n  const wchar_t* window_class =\n      WindowClassRegistrar::GetInstance()->GetWindowClass();\n\n  const POINT target_point = {static_cast<LONG>(origin.x),\n                              static_cast<LONG>(origin.y)};\n  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);\n  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);\n  double scale_factor = dpi / 96.0;\n\n  HWND window = CreateWindow(\n      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,\n      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),\n      Scale(size.width, scale_factor), Scale(size.height, scale_factor),\n      nullptr, nullptr, GetModuleHandle(nullptr), this);\n\n  if (!window) {\n    return false;\n  }\n\n  return OnCreate();\n}\n\n// static\nLRESULT CALLBACK Win32Window::WndProc(HWND const window,\n                                      UINT const message,\n                                      WPARAM const wparam,\n                                      LPARAM const lparam) noexcept {\n  if (message == WM_NCCREATE) {\n    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);\n    SetWindowLongPtr(window, GWLP_USERDATA,\n                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));\n\n    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);\n    EnableFullDpiSupportIfAvailable(window);\n    that->window_handle_ = window;\n  } else if (Win32Window* that = GetThisFromHandle(window)) {\n    return that->MessageHandler(window, message, wparam, lparam);\n  }\n\n  return DefWindowProc(window, message, wparam, lparam);\n}\n\nLRESULT\nWin32Window::MessageHandler(HWND hwnd,\n                            UINT const message,\n                            WPARAM const wparam,\n                            LPARAM const lparam) noexcept {\n  switch (message) {\n    case WM_DESTROY:\n      window_handle_ = nullptr;\n      Destroy();\n      if (quit_on_close_) {\n        PostQuitMessage(0);\n      }\n      return 0;\n\n    case WM_DPICHANGED: {\n      auto newRectSize = reinterpret_cast<RECT*>(lparam);\n      LONG newWidth = newRectSize->right - newRectSize->left;\n      LONG newHeight = newRectSize->bottom - newRectSize->top;\n\n      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,\n                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);\n\n      return 0;\n    }\n    case WM_SIZE: {\n      RECT rect = GetClientArea();\n      if (child_content_ != nullptr) {\n        // Size and position the child window.\n        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,\n                   rect.bottom - rect.top, TRUE);\n      }\n      return 0;\n    }\n\n    case WM_ACTIVATE:\n      if (child_content_ != nullptr) {\n        SetFocus(child_content_);\n      }\n      return 0;\n  }\n\n  return DefWindowProc(window_handle_, message, wparam, lparam);\n}\n\nvoid Win32Window::Destroy() {\n  OnDestroy();\n\n  if (window_handle_) {\n    DestroyWindow(window_handle_);\n    window_handle_ = nullptr;\n  }\n  if (g_active_window_count == 0) {\n    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();\n  }\n}\n\nWin32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {\n  return reinterpret_cast<Win32Window*>(\n      GetWindowLongPtr(window, GWLP_USERDATA));\n}\n\nvoid Win32Window::SetChildContent(HWND content) {\n  child_content_ = content;\n  SetParent(content, window_handle_);\n  RECT frame = GetClientArea();\n\n  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,\n             frame.bottom - frame.top, true);\n\n  SetFocus(child_content_);\n}\n\nRECT Win32Window::GetClientArea() {\n  RECT frame;\n  GetClientRect(window_handle_, &frame);\n  return frame;\n}\n\nHWND Win32Window::GetHandle() {\n  return window_handle_;\n}\n\nvoid Win32Window::SetQuitOnClose(bool quit_on_close) {\n  quit_on_close_ = quit_on_close;\n}\n\nbool Win32Window::OnCreate() {\n  // No-op; provided for subclasses.\n  return true;\n}\n\nvoid Win32Window::OnDestroy() {\n  // No-op; provided for subclasses.\n}\n"
  },
  {
    "path": "bookkeeping_user_flutter/windows/runner/win32_window.h",
    "content": "#ifndef RUNNER_WIN32_WINDOW_H_\n#define RUNNER_WIN32_WINDOW_H_\n\n#include <windows.h>\n\n#include <functional>\n#include <memory>\n#include <string>\n\n// A class abstraction for a high DPI-aware Win32 Window. Intended to be\n// inherited from by classes that wish to specialize with custom\n// rendering and input handling\nclass Win32Window {\n public:\n  struct Point {\n    unsigned int x;\n    unsigned int y;\n    Point(unsigned int x, unsigned int y) : x(x), y(y) {}\n  };\n\n  struct Size {\n    unsigned int width;\n    unsigned int height;\n    Size(unsigned int width, unsigned int height)\n        : width(width), height(height) {}\n  };\n\n  Win32Window();\n  virtual ~Win32Window();\n\n  // Creates and shows a win32 window with |title| and position and size using\n  // |origin| and |size|. New windows are created on the default monitor. Window\n  // sizes are specified to the OS in physical pixels, hence to ensure a\n  // consistent size to will treat the width height passed in to this function\n  // as logical pixels and scale to appropriate for the default monitor. Returns\n  // true if the window was created successfully.\n  bool CreateAndShow(const std::wstring& title,\n                     const Point& origin,\n                     const Size& size);\n\n  // Release OS resources associated with window.\n  void Destroy();\n\n  // Inserts |content| into the window tree.\n  void SetChildContent(HWND content);\n\n  // Returns the backing Window handle to enable clients to set icon and other\n  // window properties. Returns nullptr if the window has been destroyed.\n  HWND GetHandle();\n\n  // If true, closing this window will quit the application.\n  void SetQuitOnClose(bool quit_on_close);\n\n  // Return a RECT representing the bounds of the current client area.\n  RECT GetClientArea();\n\n protected:\n  // Processes and route salient window messages for mouse handling,\n  // size change and DPI. Delegates handling of these to member overloads that\n  // inheriting classes can handle.\n  virtual LRESULT MessageHandler(HWND window,\n                                 UINT const message,\n                                 WPARAM const wparam,\n                                 LPARAM const lparam) noexcept;\n\n  // Called when CreateAndShow is called, allowing subclass window-related\n  // setup. Subclasses should return false if setup fails.\n  virtual bool OnCreate();\n\n  // Called when Destroy is called.\n  virtual void OnDestroy();\n\n private:\n  friend class WindowClassRegistrar;\n\n  // OS callback called by message pump. Handles the WM_NCCREATE message which\n  // is passed when the non-client area is being created and enables automatic\n  // non-client DPI scaling so that the non-client area automatically\n  // responsponds to changes in DPI. All other messages are handled by\n  // MessageHandler.\n  static LRESULT CALLBACK WndProc(HWND const window,\n                                  UINT const message,\n                                  WPARAM const wparam,\n                                  LPARAM const lparam) noexcept;\n\n  // Retrieves a class instance pointer for |window|\n  static Win32Window* GetThisFromHandle(HWND const window) noexcept;\n\n  bool quit_on_close_ = false;\n\n  // window handle for top level window.\n  HWND window_handle_ = nullptr;\n\n  // window handle for hosted content.\n  HWND child_content_ = nullptr;\n};\n\n#endif  // RUNNER_WIN32_WINDOW_H_\n"
  },
  {
    "path": "bookkeeping_user_uniapp/App.vue",
    "content": "<script>\n\texport default {\n\t\tonLaunch: function() {\n\t\t\tconsole.log('App Launch')\n\t\t},\n\t\tonShow: function() {\n\t\t\tconsole.log('App Show')\n\t\t},\n\t\tonHide: function() {\n\t\t\tconsole.log('App Hide')\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\">\n\t/*每个页面公共css */\n\t@import \"@/uni_modules/uview-ui/index.scss\";\n\t.u-tabbar__content__item-wrapper {\n\t\talign-items: center !important;\n\t}\n\t.u-content {\n\t\tpadding: 0 10px;\n\t}\n\t.uni-list-item__extra-text {\n\t\tfont-size: 16px !important;\n\t}\n\t.expense .uni-list-item__extra-text {\n\t\tcolor: #3c9cff !important;\n\t}\n\t.income .uni-list-item__extra-text {\n\t\tcolor: #ff0000 !important;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/components/accounts/accounts.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<u-navbar\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t:placeholder=\"true\"\n\t\t\t:border=\"true\"\n\t\t\tleftIcon=\"reload\"\n\t\t\tleftIconSize=\"25px\"\n\t\t\t@leftClick=\"refreshData()\"\n\t\t>\n\t\t\t<view class=\"u-nav-slot\" slot=\"right\">\n\t\t\t\t<u-icon name=\"list\" size=\"25\"></u-icon>\n\t\t\t\t<u-icon name=\"plus\" size=\"25\"></u-icon>\n\t\t\t</view>\n\t\t</u-navbar>\n\t\t<u-tabs :list=\"tabList\" @click=\"tabClick\" :scrollable=\"false\"></u-tabs>\n\t\t<!-- <u-list @scrolltolower=\"scrolltolower\" :showScrollbar=\"true\">\n\t\t\t<u-list-item v-for=\"(item, index) in accounts\" :key=\"index\">\n\t\t\t\t<u-cell\n\t\t\t\t    :title=\"item.name\"\n\t\t\t\t    :value=\"item.balanceFormatted\"\n\t\t\t\t\t:isLink=\"true\" arrow-direction=\"right\"\n\t\t\t\t\t@click=\"itemClick(item)\"\n\t\t\t\t></u-cell>\n\t\t\t</u-list-item>\n\t\t\t<u-loadmore :status=\"status\" :loadmoreText=\"loadmoreText\" />\n\t\t</u-list> -->\n\t\t<uni-list>\n\t\t\t<uni-list-item v-for=\"(item, index) in accounts\" :key=\"index\" clickable\n\t\t\t\t:title=\"item.name\"\n\t\t\t\t:rightText=\"item.balanceFormatted\"\n\t\t\t\tlink @click=\"itemClick(item)\">\n\t\t\t</uni-list-item>\n\t\t</uni-list>\n\t\t<u-loading-page :loading=\"loading\"></u-loading-page>\n\t</view>\n</template>\n\n<script>\nimport { queryAccounts } from '@/config/api.js';\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\ttabList: [\n\t\t\t\t{ name: '活期' },\n\t\t\t\t{ name: '信用' },\n\t\t\t\t{ name: '贷款' },\n\t\t\t\t{ name: '资产' },\n\t\t\t],\n\t\t\tcurrentIndex: 0,\n\t\t\taccounts: [ ],\n\t\t\tqueryParams: {\n\t\t\t\tpage: 1,\n\t\t\t\tsize: 15\n\t\t\t},\n\t\t\tloading: false,\n\t\t\tstatus: 'loadmore',\n\t\t\tloadmoreText: \" \",\n\t\t}\n\t},\n\tmounted: async function() {\n\t\tawait this.fetchData();\n\t},\n\tmethods: {\n\t\ttabClick(item) {\n\t\t\tthis.currentIndex = item.index;\n\t\t\tthis.refreshData();\n\t\t},\n\t\titemClick(item) {\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/account-detail/account-detail?id=' + item.id,\n\t\t\t\tevents: {\n\t\t\t\t\tdeleteSuccess() {\n\t\t\t\t\t\t$this.refreshData();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tasync scrolltolower() {\n\t\t\tawait this.fetchData(true);\n\t\t},\n\t\tasync refreshData() {\n\t\t\tthis.accounts = [];\n\t\t\tthis.queryParams.page = 1;\n\t\t\tthis.status = 'loadmore';\n\t\t\tawait this.fetchData();\n\t\t},\n\t\tasync fetchData(isMore) {\n\t\t\tif (!isMore) this.loading = true;\n\t\t\tif (isMore) this.status = 'loading';\n\t\t\ttry{\n\t\t\t\tconst response = await queryAccounts(this.currentIndex + 1, this.queryParams);\n\t\t\t\tif (response.content.length === 0) {\n\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t} else {\n\t\t\t\t\tthis.accounts = [...this.accounts, ...response.content];\n\t\t\t\t\tif (response.content.length < this.queryParams.size) {\n\t\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.queryParams.page = this.queryParams.page + 1;\n\t\t\t\t\t\tthis.status = 'loadmore';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.loading = false;\n\t\t\t} catch(e) {\n\t\t\t\tthis.loading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\t\n\t}\n}\n</script>\n\n<style lang=\"scss\">\n.u-nav-slot {\n\t@include flex;\n\tmargin-right: 90px;\n\talign-items: center;\n\tjustify-content: space-between;\n\tborder-width: 0.5px;\n\tborder-radius: 100px;\n\tborder-color: $u-border-color;\n\tpadding: 3px 7px;\n\tgap: 10px;\n}\n</style>"
  },
  {
    "path": "bookkeeping_user_uniapp/components/expense-form/expense-form.vue",
    "content": "<template>\n\t<view class=\"u-content\">\n\t\t<u--form :model=\"form\" :rules=\"rules\" ref=\"uForm\" labelWidth=\"auto\">\n\t\t\t<u-form-item label=\"描述：\" borderBottom>\n\t\t\t\t<u--input v-model=\"form.description\" border=\"none\"></u--input>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"时间：\" prop=\"createTime\" borderBottom @click=\"openCreateTimePicker\">\n\t\t\t\t<u--input v-model=\"createTime\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择日期和时间\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"账户：\" prop=\"accountId\" borderBottom @click=\"openAccountSelect\">\n\t\t\t\t<u--input v-model=\"accountName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择账户\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"分类：\" borderBottom @click=\"openCategorySelect\">\n\t\t\t\t<u--input v-model=\"categoryName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择分类\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<template v-for=\"(item, index) in form.categories\">\n\t\t\t\t<u-form-item borderBottom :label=\"item.categoryName+'：'\">\n\t\t\t\t\t<u--input :value=\"item.amount\" type=\"digit\" border=\"none\" placeholder=\"金额\" @change=\"amountChange($event, item)\"></u--input>\n\t\t\t\t</u-form-item>\n\t\t\t\t<u-form-item v-if=\"defaultCurrencyCode !== accountCurrencyCode\" borderBottom :label=\"'折合' + defaultCurrencyCode +'：'\">\n\t\t\t\t\t<u--input :value=\"item.convertedAmount\" type=\"digit\" border=\"none\" placeholder=\"金额\" @change=\"convertedAmountChange($event, item)\"></u--input>\n\t\t\t\t</u-form-item>\n\t\t\t</template>\n\t\t\t<u-form-item label=\"交易对象：\" borderBottom @click=\"openPayeeSelect\">\n\t\t\t\t<u--input v-model=\"payeeName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择付款对象\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"close\" @click.native.stop=\"clearPayee\" :stop=\"true\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"标签：\" borderBottom @click=\"openTagSelect\">\n\t\t\t\t<u--input v-model=\"tagName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择标签\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"close\" @click.native.stop=\"clearTag\" :stop=\"true\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item v-if=\"type !== 2\" label=\"是否确认：\" borderBottom>\n\t\t\t\t<u-switch v-model=\"form.confirmed\"></u-switch>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"备注：\" borderBottom>\n\t\t\t\t<u--input v-model=\"form.notes\" border=\"none\"></u--input>\n\t\t\t</u-form-item>\n\t\t</u--form>\n\t\t<u-button type=\"primary\" @click=\"submit\" text=\"保存\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t<u-datetime-picker\n\t\t\t:show=\"showCreateTime\"\n\t\t\tv-model=\"form.createTime\"\n\t\t\tmode=\"datetime\"\n\t\t\tcloseOnClickOverlay\n\t\t\t@confirm=\"createTimeConfirm\"\n\t\t\t@cancel=\"createTimeClose\"\n\t\t\t@close=\"createTimeClose\"\n\t\t></u-datetime-picker>\n\t</view>\n</template>\n\n<script>\nimport { saveExpense, updateExpense, refundExpense } from '@/config/api.js';\nexport default {\n\t\n\tdata() {\n\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\tconst type = this.$store.getters['modelForm/type'];\n\t\tlet createTime = Number(new Date());\n\t\tif (type === 2) createTime = flow.createTime;\n\t\treturn {\n\t\t\ttype: type,\n\t\t\tform: {\n\t\t\t\tdescription: '',\n\t\t\t\tcreateTime: createTime,\n\t\t\t\taccountId: '',\n\t\t\t\tcategories: [],\n\t\t\t\tpayeeId: undefined,\n\t\t\t\ttags: [],\n\t\t\t\tconfirmed: true,\n\t\t\t\tnotes: '',\n\t\t\t},\n\t\t\tshowCreateTime: false,\n\t\t\tcreateTime: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd hh:MM'),\n\t\t\taccountName: '',\n\t\t\taccountCurrencyCode: '',\n\t\t\tcategoryName: '',\n\t\t\tpayeeName: undefined,\n\t\t\ttagName: '',\n\t\t\trules: {\n\t\t\t\t'accountId': {\n\t\t\t\t\ttype: 'number',\n\t\t\t\t\trequired: true,\n\t\t\t\t\tmessage: '请选择交易账户',\n\t\t\t\t\ttrigger: []\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t},\n\tcomputed: {\n\t\tdefaultCurrencyCode() {\n\t\t\treturn this.$store.getters.defaultCurrencyCode;\n\t\t}\n\t},\n\tmethods: {\n\t\tinitFormData() {\n\t\t\tconst type = this.$store.getters['modelForm/type'];\n\t\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\t\tif (type === 1) {\n\t\t\t\tconst defaultBook = this.$store.getters['defaultBook'];\n\t\t\t\tif (defaultBook) {\n\t\t\t\t\tif (defaultBook.defaultExpenseAccount) {\n\t\t\t\t\t\tthis.accountName = defaultBook.defaultExpenseAccount.name;\n\t\t\t\t\t\tthis.form.accountId = defaultBook.defaultExpenseAccount.id;\n\t\t\t\t\t\tthis.accountCurrencyCode = defaultBook.defaultExpenseAccount.currencyCode;\n\t\t\t\t\t}\n\t\t\t\t\tif (defaultBook.defaultExpenseCategory) {\n\t\t\t\t\t\tthis.categoryName = defaultBook.defaultExpenseCategory.name;\n\t\t\t\t\t\tthis.form.categories = [{'categoryId': defaultBook.defaultExpenseCategory.id, 'categoryName': defaultBook.defaultExpenseCategory.name, 'amount': ''}]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.form.description = flow.description;\n\t\t\t\tthis.accountName = flow.accountName;\n\t\t\t\tthis.accountCurrencyCode = flow.currencyCode;\n\t\t\t\tthis.form.accountId = flow.accountId;\n\t\t\t\tthis.categoryName = flow.categories.map(e => e.categoryName).join(', ');\n\t\t\t\tthis.form.categories = flow.categories.map(e => {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcategoryId: e.categoryId,\n\t\t\t\t\t    categoryName: e.categoryName,\n\t\t\t\t\t    amount: type == 4 ? e.amount*-1 : e.amount,\n\t\t\t\t\t    convertedAmount: type == 4 ? e.convertedAmount*-1 : e.convertedAmount\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (flow.payee) this.payeeName = flow.payee.name;\n\t\t\t\tif (flow.payee) this.form.payeeId = flow.payee.id;\n\t\t\t\tthis.tagName = flow.tags.map(e => e.tagName).join(', ');\n\t\t\t\tthis.form.tags = flow.tags.map(e => e.tagId);\n\t\t\t\tif (type === 2) {\n\t\t\t\t\tthis.form.notes = flow.notes;\n\t\t\t\t\tthis.createTime = uni.$u.timeFormat(new Date(flow.createTime), 'yyyy-mm-dd hh:MM');\n\t\t\t\t\tthis.form.confirmed = flow.status === 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// console.log(flow);\n\t\t\t//const flow = this.$store.getters.flow;\n\t\t},\n\t\topenCreateTimePicker() {\n\t\t\tthis.showCreateTime = true;\n\t\t\thideKeyboard();\n\t\t},\n\t\tcreateTimeClose() {\n\t\t\tthis.showCreateTime = false\n\t\t},\n\t\tcreateTimeConfirm(e) {\n\t\t\tthis.showCreateTime = false;\n\t\t\tthis.createTime = uni.$u.timeFormat(e.value, 'yyyy-mm-dd hh:MM')\n\t\t\t//this.form.createTime = e.value.getTime();\n\t\t},\n\t\topenAccountSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.expenseableAccounts)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择账户&value=' + $this.form.accountId,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\t$this.form.accountId = n;\n\t\t\t\t\t\tconst account = $this.$store.getters.expenseableAccounts.find(e => e.id === n);\n\t\t\t\t\t\t$this.accountName = account.name;\n\t\t\t\t\t\t$this.accountCurrencyCode = account.currencyCode;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenCategorySelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.expenseCategories)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择类别&multiple=true&value=' + $this.form.categories.map(e => e.categoryId),\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tlet categoryList = [];\n\t\t\t\t\t\tn.forEach(i => {\n\t\t\t\t\t\t\tcategoryList.push($this.$store.getters.expenseCategories.find(e => e.id === i));\n\t\t\t\t\t\t});\n\t\t\t\t\t\t$this.categoryName = categoryList.map(e => e.name).join(', ');\n\t\t\t\t\t\t$this.form.categories = categoryList.map(e => {return {'categoryId': e.id, 'categoryName': e.name, 'amount': ''}});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenPayeeSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.expenseablePayees);\n\t\t\tvar that = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择交易对象&value=' + this.form.payeeId,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tthat.form.payeeId = n;\n\t\t\t\t\t\tthat.payeeName = that.$store.getters.expenseablePayees.find(e => e.id === n).name;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tclearPayee() {\n\t\t\tthis.form.payeeId = undefined;\n\t\t\tthis.payeeName = '';\n\t\t},\n\t\topenTagSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.expenseableTags)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择标签&multiple=true&value=' + $this.form.tags,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tlet tagList = [];\n\t\t\t\t\t\tn.forEach(i => {\n\t\t\t\t\t\t\tif (!isNaN(i)) {\n\t\t\t\t\t\t\t\ttagList.push($this.$store.getters.expenseableTags.find(e => e.id === i));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\t$this.tagName = tagList.map(e => e.name).join(', ');\n\t\t\t\t\t\t$this.form.tags = tagList.map(e => e.id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tclearTag() {\n\t\t\tthis.form.tags = [ ];\n\t\t\tthis.tagName = '';\n\t\t},\n\t\tamountChange(value, item) {\n\t\t\tthis.form.categories.find(e => e.categoryId === item.categoryId).amount = value;\n\t\t},\n\t\tconvertedAmountChange(value, item) {\n\t\t\tthis.form.categories.find(e => e.categoryId === item.categoryId).convertedAmount = value;\n\t\t},\n\t\tasync submit() {\n\t\t\ttry {\n\t\t\t\tawait this.$refs.uForm.validate();\n\t\t\t\tconst type = this.$store.getters['modelForm/type'];\n\t\t\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\t\t\tif (type === 1 || type === 3) {\n\t\t\t\t\tawait saveExpense(this.form);\n\t\t\t\t} else if (type === 2) {\n\t\t\t\t\tawait updateExpense(flow.id, this.form);\n\t\t\t\t\tthis.$eventBus.$emit('flowUpdated');\n\t\t\t\t} else if (type === 4) {\n\t\t\t\t\tawait refundExpense(flow.id, this.form)\n\t\t\t\t}\n\t\t\t\tuni.$u.toast('保存成功');\n\t\t\t\tthis.$eventBus.$emit('flowsUpdated');\n\t\t\t\tuni.navigateBack();\n\t\t\t} catch (e) {\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t}\n\t},\n\tasync mounted() {\n\t\tif (!this.$store.getters.expenseableAccounts) this.$store.dispatch('getExpenseableAccounts');\n\t\tif (!this.$store.getters.expenseCategories) this.$store.dispatch('getExpenseCategories');\n\t\tif (!this.$store.getters.expenseablePayees) this.$store.dispatch('getExpenseablePayees');\n\t\tif (!this.$store.getters.expenseableTags) this.$store.dispatch('getExpenseableTags');\n\t\tthis.initFormData();\n    \tthis.$refs.uForm.setRules(this.rules)\n    },\n};\n</script>"
  },
  {
    "path": "bookkeeping_user_uniapp/components/flows/flows.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<u-navbar\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t:placeholder=\"true\"\n\t\t\t:border=\"true\"\n\t\t\tleftIcon=\"reload\"\n\t\t\tleftIconSize=\"25px\"\n\t\t\t@leftClick=\"refreshData()\"\n\t\t>\n\t\t\t<view class=\"u-nav-slot\" slot=\"right\">\n\t\t\t\t<u-icon name=\"list\" size=\"25\"></u-icon>\n\t\t\t\t<u-icon name=\"search\" size=\"25\"></u-icon>\n\t\t\t</view>\n\t\t</u-navbar>\n\t\t<u-list @scrolltolower=\"scrolltolower\" :showScrollbar=\"true\">\n\t\t\t<u-list-item v-for=\"(item, index) in flows\" :key=\"index\">\n\t\t\t\t<u-cell\n\t\t\t\t    :title=\"item.title\"\n\t\t\t\t    :value=\"item.amountFormatted\"\n\t\t\t\t    :label=\"item.subTitle\"\n\t\t\t\t\t:isLink=\"true\" arrow-direction=\"right\"\n\t\t\t\t\t@click=\"itemClick(item)\"\n\t\t\t\t></u-cell>\n\t\t\t</u-list-item>\n\t\t\t<u-loadmore :status=\"status\" :loadmoreText=\"loadmoreText\" />\n\t\t</u-list>\n\t\t<u-loading-page :loading=\"loading\"></u-loading-page>\n\t</view>\n</template>\n\n<script>\nimport { queryFlows } from '@/config/api.js';\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tflows: [],\n\t\t\tqueryParams: {\n\t\t\t\tpage: 1,\n\t\t\t\tsize: 15\n\t\t\t},\n\t\t\tloading: false,\n\t\t\tstatus: 'loadmore',\n\t\t\tloadmoreText: \" \",\n\t\t}\n\t},\n\tmounted: async function() {\n\t\tawait this.fetchData();\n\t},\n\tcreated() {\n\t\tvar $this = this;\n\t\tthis.$eventBus.$on('flowsUpdated', function() {\n\t\t\t$this.refreshData();\n\t\t});\n\t},\n\tmethods: {\n\t\tasync scrolltolower() {\n\t\t\tawait this.fetchData(true);\n\t\t},\n\t\tasync refreshData() {\n\t\t\tthis.flows = [];\n\t\t\tthis.queryParams.page = 1;\n\t\t\tthis.status = 'loadmore';\n\t\t\tawait this.fetchData();\n\t\t},\n\t\tasync fetchData(isMore) {\n\t\t\tif (!isMore) this.loading = true;\n\t\t\tif (isMore) this.status = 'loading';\n\t\t\ttry{\n\t\t\t\tconst response = await queryFlows(this.queryParams);\n\t\t\t\tif (response.result.content.length === 0) {\n\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t} else {\n\t\t\t\t\tthis.flows = [...this.flows, ...response.result.content];\n\t\t\t\t\tthis.queryParams.page = this.queryParams.page + 1;\n\t\t\t\t\tthis.status = 'loadmore';\n\t\t\t\t}\n\t\t\t\tthis.loading = false;\n\t\t\t} catch(e) {\n\t\t\t\tthis.loading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\titemClick(item) {\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/flow-detail/flow-detail?id=' + item.id,\n\t\t\t\tevents: {\n\t\t\t\t\tdeleteSuccess() {\n\t\t\t\t\t\t$this.refreshData();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n}\n</script>\n\n<style lang=\"scss\">\n.u-nav-slot {\n\t@include flex;\n\tmargin-right: 90px;\n\talign-items: center;\n\tjustify-content: space-between;\n\tborder-width: 0.5px;\n\tborder-radius: 100px;\n\tborder-color: $u-border-color;\n\tpadding: 3px 7px;\n\tgap: 10px;\n}\n</style>"
  },
  {
    "path": "bookkeeping_user_uniapp/components/income-form/income-form.vue",
    "content": "<template>\n\t<view class=\"u-content\">\n\t\t<u--form :model=\"form\" :rules=\"rules\" ref=\"uForm\" labelWidth=\"auto\">\n\t\t\t<u-form-item label=\"描述：\" borderBottom>\n\t\t\t\t<u--input v-model=\"form.description\" border=\"none\"></u--input>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"时间：\" prop=\"createTime\" borderBottom @click=\"openCreateTimePicker\">\n\t\t\t\t<u--input v-model=\"createTime\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择日期和时间\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"账户：\" prop=\"accountId\" borderBottom @click=\"openAccountSelect\">\n\t\t\t\t<u--input v-model=\"accountName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择账户\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"分类：\" borderBottom @click=\"openCategorySelect\">\n\t\t\t\t<u--input v-model=\"categoryName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择分类\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<template v-for=\"(item, index) in form.categories\">\n\t\t\t\t<u-form-item borderBottom :label=\"item.categoryName+'：'\">\n\t\t\t\t\t<u--input :value=\"item.amount\" type=\"digit\" border=\"none\" placeholder=\"金额\" @change=\"amountChange($event, item)\"></u--input>\n\t\t\t\t</u-form-item>\n\t\t\t\t<u-form-item v-if=\"defaultCurrencyCode !== accountCurrencyCode\" borderBottom :label=\"'折合' + defaultCurrencyCode +'：'\">\n\t\t\t\t\t<u--input :value=\"item.convertedAmount\" type=\"digit\" border=\"none\" placeholder=\"金额\" @change=\"convertedAmountChange($event, item)\"></u--input>\n\t\t\t\t</u-form-item>\n\t\t\t</template>\n\t\t\t<u-form-item label=\"交易对象：\" borderBottom @click=\"openPayeeSelect\">\n\t\t\t\t<u--input v-model=\"payeeName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择付款对象\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"close\" @click.native.stop=\"clearPayee\" :stop=\"true\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"标签：\" borderBottom @click=\"openTagSelect\">\n\t\t\t\t<u--input v-model=\"tagName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择标签\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"close\" @click.native.stop=\"clearTag\" :stop=\"true\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item v-if=\"type !== 2\" label=\"是否确认：\" borderBottom>\n\t\t\t\t<u-switch v-model=\"form.confirmed\"></u-switch>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"备注：\" borderBottom>\n\t\t\t\t<u--input v-model=\"form.notes\" border=\"none\"></u--input>\n\t\t\t</u-form-item>\n\t\t</u--form>\n\t\t<u-button type=\"primary\" @click=\"submit\" text=\"保存\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t<u-datetime-picker\n\t\t\t:show=\"showCreateTime\"\n\t\t\tv-model=\"form.createTime\"\n\t\t\tmode=\"datetime\"\n\t\t\tcloseOnClickOverlay\n\t\t\t@confirm=\"createTimeConfirm\"\n\t\t\t@cancel=\"createTimeClose\"\n\t\t\t@close=\"createTimeClose\"\n\t\t></u-datetime-picker>\n\t</view>\n</template>\n\n<script>\nimport { saveIncome, updateIncome, refundIncome } from '@/config/api.js';\nexport default {\n\t\n\tdata() {\n\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\tconst type = this.$store.getters['modelForm/type'];\n\t\tlet createTime = Number(new Date());\n\t\tif (type === 2) createTime = flow.createTime;\n\t\treturn {\n\t\t\ttype: type,\n\t\t\tform: {\n\t\t\t\tdescription: '',\n\t\t\t\tcreateTime: createTime,\n\t\t\t\taccountId: '',\n\t\t\t\tcategories: [],\n\t\t\t\tpayeeId: '',\n\t\t\t\ttags: [],\n\t\t\t\tconfirmed: true,\n\t\t\t\tnotes: '',\n\t\t\t},\n\t\t\tshowCreateTime: false,\n\t\t\tcreateTime: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd hh:MM'),\n\t\t\taccountName: '',\n\t\t\taccountCurrencyCode: '',\n\t\t\tcategoryName: '',\n\t\t\tpayeeName: '',\n\t\t\ttagName: '',\n\t\t\trules: {\n\t\t\t\t'accountId': {\n\t\t\t\t\ttype: 'number',\n\t\t\t\t\trequired: true,\n\t\t\t\t\tmessage: '请选择交易账户',\n\t\t\t\t\ttrigger: []\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t},\n\tcomputed: {\n\t\tdefaultCurrencyCode() {\n\t\t\treturn this.$store.getters.defaultCurrencyCode;\n\t\t}\n\t},\n\tmethods: {\n\t\tinitFormData() {\n\t\t\tconst type = this.$store.getters['modelForm/type'];\n\t\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\t\tif (type === 1) {\n\t\t\t\tconst defaultBook = this.$store.getters['defaultBook'];\n\t\t\t\tif (defaultBook) {\n\t\t\t\t\tif (defaultBook.defaultIncomeAccount) {\n\t\t\t\t\t\tthis.accountName = defaultBook.defaultIncomeAccount.name;\n\t\t\t\t\t\tthis.form.accountId = defaultBook.defaultIncomeAccount.id;\n\t\t\t\t\t\tthis.accountCurrencyCode = defaultBook.defaultExpenseAccount.currencyCode;\n\t\t\t\t\t}\n\t\t\t\t\tif (defaultBook.defaultIncomeCategory) {\n\t\t\t\t\t\tthis.categoryName = defaultBook.defaultIncomeCategory.name;\n\t\t\t\t\t\tthis.form.categories = [{'categoryId': defaultBook.defaultIncomeCategory.id, 'categoryName': defaultBook.defaultIncomeCategory.name, 'amount': ''}]\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.form.description = flow.description;\n\t\t\t\tthis.accountName = flow.accountName;\n\t\t\t\tthis.accountCurrencyCode = flow.currencyCode;\n\t\t\t\tthis.form.accountId = flow.accountId;\n\t\t\t\tthis.categoryName = flow.categories.map(e => e.categoryName).join(', ');\n\t\t\t\tthis.form.categories = flow.categories.map(e => {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcategoryId: e.categoryId,\n\t\t\t\t\t    categoryName: e.categoryName,\n\t\t\t\t\t    amount: type == 4 ? e.amount*-1 : e.amount,\n\t\t\t\t\t    convertedAmount: type == 4 ? e.convertedAmount*-1 : e.convertedAmount\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (flow.payee) this.payeeName = flow.payee.name;\n\t\t\t\tif (flow.payee) this.form.payeeId = flow.payee.id;\n\t\t\t\tthis.tagName = flow.tags.map(e => e.tagName).join(', ');\n\t\t\t\tthis.form.tags = flow.tags.map(e => e.tagId);\n\t\t\t\tif (type === 2) {\n\t\t\t\t\tthis.form.notes = flow.notes;\n\t\t\t\t\tthis.createTime = uni.$u.timeFormat(new Date(flow.createTime), 'yyyy-mm-dd hh:MM');\n\t\t\t\t\tthis.form.confirmed = flow.status === 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// console.log(flow);\n\t\t\t//const flow = this.$store.getters.flow;\n\t\t},\n\t\topenCreateTimePicker() {\n\t\t\tthis.showCreateTime = true;\n\t\t\thideKeyboard();\n\t\t},\n\t\tcreateTimeClose() {\n\t\t\tthis.showCreateTime = false\n\t\t},\n\t\tcreateTimeConfirm(e) {\n\t\t\tthis.showCreateTime = false;\n\t\t\tthis.createTime = uni.$u.timeFormat(e.value, 'yyyy-mm-dd hh:MM')\n\t\t\t//this.form.createTime = e.value.getTime();\n\t\t},\n\t\topenAccountSelect() {\n\t\t\tconsole.log(this.$store.getters.incomeableAccounts)\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.incomeableAccounts)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择账户&value=' + $this.form.accountId,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\t$this.form.accountId = n;\n\t\t\t\t\t\tconst account = $this.$store.getters.incomeableAccounts.find(e => e.id === n);\n\t\t\t\t\t\t$this.accountName = account.name;\n\t\t\t\t\t\t$this.accountCurrencyCode = account.currencyCode;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenCategorySelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.incomeCategories)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择类别&multiple=true&value=' + $this.form.categories.map(e => e.categoryId),\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tlet categoryList = [];\n\t\t\t\t\t\tn.forEach(i => {\n\t\t\t\t\t\t\tcategoryList.push($this.$store.getters.incomeCategories.find(e => e.id === i));\n\t\t\t\t\t\t});\n\t\t\t\t\t\t$this.categoryName = categoryList.map(e => e.name).join(', ');\n\t\t\t\t\t\t$this.form.categories = categoryList.map(e => {return {'categoryId': e.id, 'categoryName': e.name, 'amount': ''}});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenPayeeSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.incomeablePayees);\n\t\t\tvar that = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择交易对象&value=' + this.form.payeeId,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tthat.form.payeeId = n;\n\t\t\t\t\t\tthat.payeeName = that.$store.getters.incomeablePayees.find(e => e.id === n).name;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tclearPayee() {\n\t\t\tthis.form.payeeId = undefined;\n\t\t\tthis.payeeName = '';\n\t\t},\n\t\topenTagSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.incomeableTags)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择标签&multiple=true&value=' + $this.form.tags,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tlet tagList = [];\n\t\t\t\t\t\tn.forEach(i => {\n\t\t\t\t\t\t\tif (!isNaN(i)) {\n\t\t\t\t\t\t\t\ttagList.push($this.$store.getters.incomeableTags.find(e => e.id === i));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\t$this.tagName = tagList.map(e => e.name).join(', ');\n\t\t\t\t\t\t$this.form.tags = tagList.map(e => e.id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tclearTag() {\n\t\t\tthis.form.tags = [ ];\n\t\t\tthis.tagName = '';\n\t\t},\n\t\tamountChange(value, item) {\n\t\t\tthis.form.categories.find(e => e.categoryId === item.categoryId).amount = value;\n\t\t},\n\t\tconvertedAmountChange(value, item) {\n\t\t\tthis.form.categories.find(e => e.categoryId === item.categoryId).convertedAmount = value;\n\t\t},\n\t\tasync submit() {\n\t\t\ttry {\n\t\t\t\tawait this.$refs.uForm.validate();\n\t\t\t\tconst type = this.$store.getters['modelForm/type'];\n\t\t\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\t\t\tif (type === 1 || type === 3) {\n\t\t\t\t\tawait saveIncome(this.form);\n\t\t\t\t} else if (type === 2) {\n\t\t\t\t\tawait updateIncome(flow.id, this.form);\n\t\t\t\t\tthis.$eventBus.$emit('flowUpdated');\n\t\t\t\t} else if (type === 4) {\n\t\t\t\t\tawait refundIncome(flow.id, this.form)\n\t\t\t\t}\n\t\t\t\tuni.$u.toast('保存成功');\n\t\t\t\tthis.$eventBus.$emit('flowsUpdated');\n\t\t\t\tuni.navigateBack();\n\t\t\t} catch (e) {\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t}\n\t},\n\tasync mounted() {\n\t\tif (!this.$store.getters.incomeableAccounts) this.$store.dispatch('getIncomeableAccounts');\n\t\tif (!this.$store.getters.incomeCategories) this.$store.dispatch('getIncomeCategories');\n\t\tif (!this.$store.getters.incomeablePayees) this.$store.dispatch('getIncomeablePayees');\n\t\tif (!this.$store.getters.incomeableTags) this.$store.dispatch('getIncomeableTags');\n\t\tthis.initFormData();\n    \tthis.$refs.uForm.setRules(this.rules)\n    },\n};\n</script>"
  },
  {
    "path": "bookkeeping_user_uniapp/components/tab-bar/tab-bar.vue",
    "content": "<template>\n\t<view>\n\t\t<u-tabbar\n\t\t\t:value=\"selectedIndex\"\n\t\t\t@change=\"tabChange\"\n\t\t\t:fixed=\"true\"\n\t\t\t:placeholder=\"true\">\n\t\t\t<u-tabbar-item text=\"账户\" icon=\"home\"></u-tabbar-item>\n\t\t\t<u-tabbar-item text=\"流水\" icon=\"list-dot\"></u-tabbar-item>\n\t\t\t<u-button type=\"primary\" text=\"记账\" shape=\"circle\" @click=\"addFlow\"></u-button>\n\t\t\t<u-tabbar-item text=\"图表\" icon=\"eye\"></u-tabbar-item>\n\t\t\t<u-tabbar-item text=\"我的\" icon=\"account\"></u-tabbar-item>\n\t\t</u-tabbar>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tprops: {\n\t\t\tselectedIndex: {\n\t\t\t\ttype: Number,\n\t\t\t\trequired: true\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\ttabChange(value) {\n\t\t\t\tswitch (value) {\n\t\t\t\t\tcase 0:\n\t\t\t\t\t\tuni.reLaunch({ url: '/pages/accounts/accounts' });\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\n\t\t\t\t\t\tuni.reLaunch({ url: '/pages/flows/flows' });\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\n\t\t\t\t\t\tuni.reLaunch({ url: '/pages/charts/charts' });\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\n\t\t\t\t\t\tuni.reLaunch({ url: '/pages/my/my' });\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\taddFlow() {\n\t\t\t\t//不能在navigateTo的success回调执行，否则微信小程序第一次加载是空白\n\t\t\t\tthis.$store.commit('modelForm/setType', 1);\n\t\t\t\tthis.$store.commit('modelForm/setModel', { });\n\t\t\t\tuni.navigateTo({ url: '/pages/flow-form/flow-form' });\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style>\n\n</style>"
  },
  {
    "path": "bookkeeping_user_uniapp/components/transfer-form/transfer-form.vue",
    "content": "<template>\n\t<view class=\"u-content\">\n\t\t<u--form :model=\"form\" :rules=\"rules\" ref=\"uForm\" labelWidth=\"auto\">\n\t\t\t<u-form-item label=\"描述：\" borderBottom>\n\t\t\t\t<u--input v-model=\"form.description\" border=\"none\"></u--input>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"时间：\" prop=\"createTime\" borderBottom @click=\"openCreateTimePicker\">\n\t\t\t\t<u--input v-model=\"createTime\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择日期和时间\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"转出账户：\" prop=\"fromId\" borderBottom @click=\"openFromAccountSelect\">\n\t\t\t\t<u--input v-model=\"fromAccountName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择账户\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"转入账户：\" prop=\"toId\" borderBottom @click=\"openToAccountSelect\">\n\t\t\t\t<u--input v-model=\"toAccountName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择账户\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"金额：\" prop=\"amount\" borderBottom>\n\t\t\t\t<u--input v-model=\"form.amount\" border=\"none\"></u--input>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item v-if=\"needConvert\" :label=\"'折合' + toAccountCurrencyCode +'：'\" prop=\"convertedAmount\" borderBottom>\n\t\t\t\t<u--input v-model=\"form.convertedAmount\" border=\"none\"></u--input>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"标签：\" borderBottom @click=\"openTagSelect\">\n\t\t\t\t<u--input v-model=\"tagName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择标签\" border=\"none\"></u--input>\n\t\t\t\t<u-icon slot=\"right\" name=\"close\" @click.native.stop=\"clearTag\" :stop=\"true\"></u-icon>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item v-if=\"type !== 2\" label=\"是否确认：\" borderBottom>\n\t\t\t\t<u-switch v-model=\"form.confirmed\"></u-switch>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"备注：\" borderBottom>\n\t\t\t\t<u--input v-model=\"form.notes\" border=\"none\"></u--input>\n\t\t\t</u-form-item>\n\t\t</u--form>\n\t\t<u-button type=\"primary\" @click=\"submit\" text=\"保存\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t<u-datetime-picker\n\t\t\t:show=\"showCreateTime\"\n\t\t\tv-model=\"form.createTime\"\n\t\t\tmode=\"datetime\"\n\t\t\tcloseOnClickOverlay\n\t\t\t@confirm=\"createTimeConfirm\"\n\t\t\t@cancel=\"createTimeClose\"\n\t\t\t@close=\"createTimeClose\"\n\t\t></u-datetime-picker>\n\t</view>\n</template>\n\n<script>\nimport { saveTransfer, updateTransfer } from '@/config/api.js';\nexport default {\n\t\n\tdata() {\n\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\tconst type = this.$store.getters['modelForm/type'];\n\t\tlet createTime = Number(new Date());\n\t\tif (type === 2) createTime = flow.createTime;\n\t\treturn {\n\t\t\ttype: type,\n\t\t\tform: {\n\t\t\t\tdescription: '',\n\t\t\t\tcreateTime: createTime,\n\t\t\t\tfromId: '',\n\t\t\t\ttoId: '',\n\t\t\t\tamount: undefined,\n\t\t\t\tconvertedAmount: undefined,\n\t\t\t\ttags: [],\n\t\t\t\tconfirmed: true,\n\t\t\t\tnotes: '',\n\t\t\t},\n\t\t\tshowCreateTime: false,\n\t\t\tcreateTime: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd hh:MM'),\n\t\t\tfromAccountName: '',\n\t\t\tfromAccountCurrencyCode: undefined,\n\t\t\ttoAccountName: '',\n\t\t\ttoAccountCurrencyCode: undefined,\n\t\t\ttagName: '',\n\t\t\trules: {\n\t\t\t\t'fromId': {\n\t\t\t\t\ttype: 'number',\n\t\t\t\t\trequired: true,\n\t\t\t\t\tmessage: '请选择转出账户',\n\t\t\t\t\ttrigger: []\n\t\t\t\t},\n\t\t\t\t'toId': {\n\t\t\t\t\ttype: 'number',\n\t\t\t\t\trequired: true,\n\t\t\t\t\tmessage: '请选择转入账户',\n\t\t\t\t\ttrigger: []\n\t\t\t\t},\n\t\t\t\t'amount': {\n\t\t\t\t\ttype: 'number',\n\t\t\t\t\trequired: true,\n\t\t\t\t\tmessage: '请输入金额',\n\t\t\t\t\ttrigger: ['blur', 'change']\n\t\t\t\t},\n\t\t\t\t'convertedAmount': {\n\t\t\t\t\ttype: 'number',\n\t\t\t\t\trequired: true,\n\t\t\t\t\tmessage: '请输入金额',\n\t\t\t\t\ttrigger: ['blur', 'change']\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t},\n\tcomputed: {\n\t\tneedConvert() {\n\t\t\tconst type = this.$store.getters['modelForm/type'];\n\t\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\t\tif (type === 3) return flow.needConvert; //复制的时候\n\t\t\tif (this.fromAccountCurrencyCode && this.toAccountCurrencyCode) {\n\t\t\t\tif (this.fromAccountCurrencyCode !== this.toAccountCurrencyCode) {\n\t\t\t\t\treturn true;\n\t\t\t\t} else {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t},\n\tmethods: {\n\t\tinitFormData() {\n\t\t\tconst type = this.$store.getters['modelForm/type'];\n\t\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\t\tif (type === 1) {\n\t\t\t\tconst defaultBook = this.$store.getters['defaultBook'];\n\t\t\t\tif (defaultBook) {\n\t\t\t\t\tif (defaultBook.defaultTransferFromAccount) {\n\t\t\t\t\t\tthis.fromAccountName = defaultBook.defaultTransferFromAccount.name;\n\t\t\t\t\t\tthis.fromAccountCurrencyCode = defaultBook.defaultTransferFromAccount.currencyCode;\n\t\t\t\t\t\tthis.form.fromId = defaultBook.defaultTransferFromAccount.id;\n\t\t\t\t\t}\n\t\t\t\t\tif (defaultBook.defaultTransferToAccount) {\n\t\t\t\t\t\tthis.toAccountName = defaultBook.defaultTransferToAccount.name;\n\t\t\t\t\t\tthis.toAccountCurrencyCode = defaultBook.defaultTransferFromAccount.currencyCode;\n\t\t\t\t\t\tthis.form.toId = defaultBook.defaultTransferToAccount.id;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.form.description = flow.description;\n\t\t\t\tthis.fromAccountName = flow.fromAccountName;\n\t\t\t\tthis.fromAccountCurrencyCode = flow.currencyCode;\n\t\t\t\tthis.form.fromId = flow.accountId;\n\t\t\t\tthis.toAccountName = flow.toAccountName;\n\t\t\t\tthis.toAccountCurrencyCode = flow.toCurrencyCode;\n\t\t\t\tthis.form.toId = flow.toId;\n\t\t\t\tthis.form.amount = flow.amount;\n\t\t\t\tthis.form.convertedAmount = flow.convertedAmount;\n\t\t\t\tthis.tagName = flow.tags.map(e => e.tagName).join(', ');\n\t\t\t\tthis.form.tags = flow.tags.map(e => e.tagId);\n\t\t\t\tif (type === 2) {\n\t\t\t\t\tconsole.log(flow)\n\t\t\t\t\tthis.form.notes = flow.notes;\n\t\t\t\t\tthis.createTime = uni.$u.timeFormat(new Date(flow.createTime), 'yyyy-mm-dd hh:MM');\n\t\t\t\t\tthis.form.confirmed = flow.status === 1;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\topenCreateTimePicker() {\n\t\t\tthis.showCreateTime = true;\n\t\t\thideKeyboard();\n\t\t},\n\t\tcreateTimeClose() {\n\t\t\tthis.showCreateTime = false\n\t\t},\n\t\tcreateTimeConfirm(e) {\n\t\t\tthis.showCreateTime = false;\n\t\t\tthis.createTime = uni.$u.timeFormat(e.value, 'yyyy-mm-dd hh:MM')\n\t\t},\n\t\topenFromAccountSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.transferFromAbleAccounts)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择账户&value=' + $this.form.fromId,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\t$this.form.fromId = n;\n\t\t\t\t\t\tconst account = $this.$store.getters.transferFromAbleAccounts.find(e => e.id === n);\n\t\t\t\t\t\t$this.fromAccountName = account.name;\n\t\t\t\t\t\t$this.fromAccountCurrencyCode = account.currencyCode;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenToAccountSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.transferToAbleAccounts)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择账户&value=' + $this.form.toId,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\t$this.form.toId = n;\n\t\t\t\t\t\tconst account = $this.$store.getters.transferToAbleAccounts.find(e => e.id === n);\n\t\t\t\t\t\t$this.toAccountName = account.name;\n\t\t\t\t\t\t$this.toAccountCurrencyCode = account.currencyCode;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenTagSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.transferableTags)\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择标签&multiple=true&value=' + $this.form.tags,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tlet tagList = [];\n\t\t\t\t\t\tn.forEach(i => {\n\t\t\t\t\t\t\tif (!isNaN(i)) {\n\t\t\t\t\t\t\t\ttagList.push($this.$store.getters.transferableTags.find(e => e.id === i));\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\t$this.tagName = tagList.map(e => e.name).join(', ');\n\t\t\t\t\t\t$this.form.tags = tagList.map(e => e.id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tclearTag() {\n\t\t\tthis.form.tags = [ ];\n\t\t\tthis.tagName = '';\n\t\t},\n\t\tasync submit() {\n\t\t\ttry {\n\t\t\t\tawait this.$refs.uForm.validate();\n\t\t\t\tconst type = this.$store.getters['modelForm/type'];\n\t\t\t\tconst flow = this.$store.getters['modelForm/model'];\n\t\t\t\tif (type === 1 || type === 3) {\n\t\t\t\t\tawait saveTransfer(this.form);\n\t\t\t\t} else if (type === 2) {\n\t\t\t\t\tawait updateTransfer(flow.id, this.form);\n\t\t\t\t\tthis.$eventBus.$emit('flowUpdated');\n\t\t\t\t}\n\t\t\t\tuni.$u.toast('保存成功');\n\t\t\t\tthis.$eventBus.$emit('flowsUpdated');\n\t\t\t\tuni.navigateBack();\n\t\t\t} catch (e) {\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t}\n\t},\n\tasync mounted() {\n\t\tif (!this.$store.getters.transferFromAbleAccounts) this.$store.dispatch('getTransferFromAbleAccounts');\n\t\tif (!this.$store.getters.transferToAbleAccounts) this.$store.dispatch('getTransferToAbleAccounts');\n\t\tif (!this.$store.getters.transferableTags) this.$store.dispatch('getTransferableTags');\n\t\tthis.initFormData();\n    \tthis.$refs.uForm.setRules(this.rules)\n    },\n};\n</script>"
  },
  {
    "path": "bookkeeping_user_uniapp/config/api.js",
    "content": "const http = uni.$u.http\n\nexport const signin = (params) => http.post('signin', params)\n\nexport const getSession = () => http.get('session')\n\nexport const queryFlows = (params) => http.get('flows', { params: params })\n\nexport const getFlowById = (id) => http.get('flows/' + id);\n\nexport const deleteFlowById = (id) => http.delete('flows/' + id)\n\nexport const confirmFlow = (id) => http.put('flows/' + id + '/confirm')\n\nexport const saveExpense = (params) => http.post('expenses', params)\n\nexport const updateExpense = (id, params) => http.put('expenses/' + id, params)\n\nexport const refundExpense = (id, params) => http.post('expenses/' + id + '/refund' , params)\n\nexport const saveIncome = (params) => http.post('incomes', params)\n\nexport const updateIncome = (id, params) => http.put('incomes/' + id, params)\n\nexport const refundIncome = (id, params) => http.post('incomes/' + id + '/refund' , params)\n\nexport const saveTransfer = (params) => http.post('transfers', params)\n\nexport const updateTransfer = (id, params) => http.put('transfers/' + id, params)\n\nexport const getEnableAccounts = () => http.get('accounts/enable')\n\nexport const getExpenseableAccounts = () => http.get('accounts/expenseable')\n\nexport const getIncomeableAccounts = () => http.get('accounts/incomeable')\n\nexport const getTransferFromAbleAccounts = () => http.get('accounts/transfer-from-able')\n\nexport const getTransferToAbleAccounts = () => http.get('accounts/transfer-to-able')\n\nexport const getExpenseCategories = () => http.get('expense-categories/enable');\n\nexport const getIncomeCategories = () => http.get('income-categories/enable');\n\nexport const getExpenseablePayees = () => http.get('payees/expenseable')\n\nexport const getIncomeablePayees = () => http.get('payees/incomeable')\n\nexport const getEnablePayees = () => http.get('payees/enable')\n\nexport const getExpenseableTags = () => http.get('tags/expenseable')\n\nexport const getIncomeableTags = () => http.get('tags/incomeable')\n\nexport const getTransferableTags = () => http.get('tags/transferable')\n\nexport const getEnableTags = () => http.get('tags/enable')\n\nexport const queryAccounts = (type, params) => {\n\tif (type === 1) {\n\t\treturn http.get('checking-accounts', { params: params });\n\t} else if (type === 2) {\n\t\treturn http.get('credit-accounts', { params: params });\n\t} else if (type === 3) {\n\t\treturn http.get('debt-accounts', { params: params });\n\t} else if (type === 4) {\n\t\treturn http.get('asset-accounts', { params: params });\n\t}\n}\n\nexport const getAccountById = (id) => http.get('accounts/' + id);\n\nexport const deleteAccountById = (id) => http.delete('accounts/' + id);\n\nexport const queryBooks = (params) => http.get('books', { params: params });\n\nexport const setDefaultBook = (id) => http.put('setDefaultBook/' + id);\n\nexport const reportExpenseCategory = (params) => http.get('reports/expense-category', { params: params });\n\nexport const reportIncomeCategory = (params) => http.get('reports/income-category', { params: params });\n\nexport const reportDebt = (params) => http.get('reports/debt', { params: params });\n\nexport const reportAsset = (params) => http.get('reports/asset', { params: params });"
  },
  {
    "path": "bookkeeping_user_uniapp/config/request.js",
    "content": "// 此vm参数为页面的实例，可以通过它引用vuex中的变量\nmodule.exports = (vm) => {\n    // 初始化请求配置\n    uni.$u.http.setConfig((config) => {\n        /* config 为默认全局配置*/\n        config.baseURL = 'https://testjz.jiukuaitech.com/api/v1/'; /* 根域名 */\n\t\t// config.baseURL = '/api/v1/'; /* 根域名 */\n        return config;\n    })\n\t\n\t// 请求拦截\n\tuni.$u.http.interceptors.request.use((config) => { // 可使用async await 做异步操作\n\t    // 初始化请求拦截器时，会执行此方法，此时data为undefined，赋予默认{}\n\t    config.data = config.data || {}\n\t\t// 根据custom参数中配置的是否需要token，添加对应的请求头\n\t\t// if(config?.custom?.auth) {\n\t\t// \t// 可以在此通过vm引用vuex中的变量，具体值在vm.$store.state中\n\t\t// \tconfig.header.token = vm.$store.state.userInfo.token\n\t\t// }\n\t\tconfig.header['user-token'] = vm.$store.getters.userToken;\n\t    return config \n\t}, config => { // 可使用async await 做异步操作\n\t    return Promise.reject(config)\n\t})\n\t\n\t// 响应拦截\n\tuni.$u.http.interceptors.response.use((response) => { /* 对响应成功做点什么 可使用async await 做异步操作*/\n\t\tconst data = response.data\n\t\t// 自定义参数\n\t\tconst custom = response.config?.custom\n\t\tif (response.statusCode !== 200) { \n\t\t\t// 如果没有显式定义custom的toast参数为false的话，默认对报错进行toast弹出提示\n\t\t\tif (custom.toast !== false) {\n\t\t\t\tuni.$u.toast(data.message)\n\t\t\t}\n\t\t\t// 如果需要catch返回，则进行reject\n\t\t\tif (custom?.catch) {\n\t\t\t\treturn Promise.reject(data)\n\t\t\t} else {\n\t\t\t\t// 否则返回一个pending中的promise，请求不会进入catch中\n\t\t\t\treturn new Promise(() => { })\n\t\t\t}\n\t\t} else {\n\t\t\tif (data.success) {\n\t\t\t\treturn data.data;\n\t\t\t} else {\n\t\t\t\tuni.$u.toast(data.errorMsg)\n\t\t\t\treturn Promise.reject(data)\n\t\t\t}\n\t\t}\n\t\treturn data.data === undefined ? {} : data.data\n\t}, (response) => { \n\t\t/*  对响应错误做点什么 （statusCode !== 200）*/\n\t\t// 对响应错误做点什么 （statusCode !== 200\n\t\tuni.$u.toast(response.errMsg);\n\t\t// uni.$u.toast('网络错误，稍后重试')\n\t\treturn Promise.reject(response)\n\t})\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/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": "bookkeeping_user_uniapp/main.js",
    "content": "import Vue from 'vue'\nimport uView from '@/uni_modules/uview-ui'\n\nimport App from './App'\nimport store from './store'\nVue.prototype.$store = store\n\nvar EventBus = new Vue()\nVue.prototype.$eventBus = EventBus\n\nVue.config.productionTip = false\nApp.mpType = 'app'\n\nVue.use(uView)\n//uni.$u.config.unit = 'rpx'\n\nconst app = new Vue({\n    ...App,\n\tstore\n})\n\nrequire('@/config/request.js')(app)\n\napp.$mount()"
  },
  {
    "path": "bookkeeping_user_uniapp/manifest.json",
    "content": "{\n    \"name\" : \"bookkeeping_user_uniapp\",\n    \"appid\" : \"__UNI__ED9932E\",\n    \"description\" : \"\",\n    \"versionName\" : \"1.0.0\",\n    \"versionCode\" : \"100\",\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        /* 应用发布信息 */\n        \"distribute\" : {\n            /* android打包配置 */\n            \"android\" : {\n                \"permissions\" : [\n                    \"<uses-permission android:name=\\\"android.permission.CHANGE_NETWORK_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.VIBRATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.READ_LOGS\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.ACCESS_WIFI_STATE\\\"/>\",\n                    \"<uses-feature android:name=\\\"android.hardware.camera.autofocus\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.ACCESS_NETWORK_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.CAMERA\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.GET_ACCOUNTS\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.READ_PHONE_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.CHANGE_WIFI_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.WAKE_LOCK\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.FLASHLIGHT\\\"/>\",\n                    \"<uses-feature android:name=\\\"android.hardware.camera\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.WRITE_SETTINGS\\\"/>\"\n                ]\n            },\n            /* ios打包配置 */\n            \"ios\" : {},\n            /* SDK配置 */\n            \"sdkConfigs\" : {}\n        }\n    },\n    /* 快应用特有相关 */\n    \"quickapp\" : {},\n    /* 小程序特有相关 */\n    \"mp-weixin\" : {\n        \"appid\" : \"wxc0f1ef35c3bfcae4\",\n        \"setting\" : {\n            \"urlCheck\" : false\n        },\n        \"usingComponents\" : true\n    },\n    \"mp-alipay\" : {\n        \"usingComponents\" : true\n    },\n    \"mp-baidu\" : {\n        \"usingComponents\" : true\n    },\n    \"mp-toutiao\" : {\n        \"usingComponents\" : true\n    },\n    \"h5\" : {\n        \"devServer\" : {\n            // \"port\" : 80, //端口\n            \"disableHostCheck\" : true,\n            \"proxy\" : {\n                //使用代理\n                \"/api/v1\" : {\n                    // \"target\": \"http://47.115.83.135/api/v2\",\n                    \"target\" : \"https://testjz.jiukuaitech.com\",\n                    \"changeOrigin\" : true,\n                    \"pathRewrite\" : {}\n                }\n            }\n        },\n        //\"^/api/v1\" : \"\"\n        \"title\" : \"九快记账\"\n    },\n    \"uniStatistics\" : {\n        \"enable\" : false\n    },\n    \"vueVersion\" : \"2\"\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/package.json",
    "content": "{\n\t\"id\": \"uview-ui\",\n\t\"scripts\": {\n\t\t\"test\": \"eslint . --fix\"\n\t},\n\t\"dependencies\": {},\n\t\"devDependencies\": {\n\t\t\"eslint\": \"^8.2.0\",\n\t\t\"eslint-config-airbnb\": \"^19.0.0\"\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/account-detail/account-detail.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<view v-if=\"!loading\" class=\"u-content\">\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\"></u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">\n\t\t\t\t<u-button type=\"primary\" text=\"修改\" size=\"mini\" customStyle=\"margin-right: 5px\" @click=\"updateAccount\"></u-button>\n\t\t\t\t<u-button type=\"primary\" text=\"禁用\" size=\"mini\" customStyle=\"margin-right: 5px\"></u-button>\n\t\t\t\t<u-button type=\"primary\" text=\"删除\" size=\"mini\" @click=\"showDeleteModal = true\"></u-button>\n\t\t\t</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">账户类型：{{account.typeName}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">账户名称：{{account.name}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">\n\t\t\t\t余额：{{account.balance}}<u--text @click=\"adjustBalance\" type=\"primary\" customStyle=\"margin-left: 10px\" text=\"余额调整\"></u--text>\n\t\t\t</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">币种：{{account.currencyCode}}</u-row>\n\t\t\t<u-row v-if=\"account.currencyCode !== defaultCurrencyCode\" customStyle=\"margin-bottom: 10px\">\n\t\t\t\t折合{{defaultCurrencyCode}}：{{account.convertedBalance}}\n\t\t\t</u-row>\n\t\t\t<u-row v-if=\"account.type === 2\" customStyle=\"margin-bottom: 10px\">信用额度：{{account.limit}}</u-row>\n\t\t\t<u-row v-if=\"account.type === 2\" customStyle=\"margin-bottom: 10px\">剩余额度：{{account.remainLimit}}</u-row>\n\t\t\t<u-row v-if=\"account.type === 2\" customStyle=\"margin-bottom: 10px\">账单日：{{account.billDay}}</u-row>\n\t\t\t<u-row v-if=\"account.type === 3\" customStyle=\"margin-bottom: 10px\">年化利率：{{account.apr}}</u-row>\n\t\t\t<u-row v-if=\"account.type === 4\" customStyle=\"margin-bottom: 10px\">更新日期：{{account.asOfDate}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">是否可用：{{account.enable ? '是' : '否'}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">是否计入净资产：{{account.include ? '是' : '否'}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">是否可支出：{{account.expenseable ? '是' : '否'}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">是否可收入：{{account.incomeable ? '是' : '否'}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">是否可转入：{{account.transferToAble ? '是' : '否'}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">是否可转出：{{account.transferFromAble ? '是' : '否'}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">初始余额：{{account.initialBalance}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">备注：{{account.notes}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">卡号：{{account.no}} <u--text v-if=\"account.no\" @click=\"copyNo\" type=\"primary\" customStyle=\"margin-left: 10px\" text=\"复制卡号\"></u--text></u-row>\n\t\t</view>\n\t\t<u-loading-page :loading=\"loading\"></u-loading-page>\n\t\t<u-modal\n\t\t\tcontent=\"删除后无法恢复，确认此操作吗？\"\n\t\t\t:show=\"showDeleteModal\"\n\t\t\tshowCancelButton\n\t\t\tcloseOnClickOverlay\n\t\t\t@confirm=\"deleteHandler\"\n\t\t\t@cancel=\"showDeleteModal = false\"\n\t\t\t@close=\"showDeleteModal = false\"\n\t\t></u-modal>\n\t</view>\n</template>\n\n<script>\nimport { getAccountById, deleteAccountById } from '@/config/api.js';\n\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tid: undefined,\n\t\t\taccount: undefined,\n\t\t\tloading: true,\n\t\t\tshowDeleteModal: false,\n\t\t\tdeleteLoading: false,\n\t\t}\n\t},\n\tcomputed: {\n\t\tdefaultCurrencyCode() {\n\t\t\treturn this.$store.getters.defaultCurrencyCode;\n\t\t}\n\t},\n\tmethods: {\n\t\tasync deleteHandler() {\n\t\t\tthis.deleteLoading = true;\n\t\t\ttry {\n\t\t\t\tawait deleteAccountById(this.account.id);\n\t\t\t\tuni.$u.toast('操作成功');\n\t\t\t\tconst eventChannel = this.getOpenerEventChannel();\n\t\t\t\teventChannel.emit('deleteSuccess');\n\t\t\t\tuni.navigateBack();\n\t\t\t\tthis.deleteLoading = false;\n\t\t\t} catch(e) {\n\t\t\t\tthis.deleteLoading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\tasync refresh() {\n\t\t\tthis.loading = true;\n\t\t\ttry{\n\t\t\t\tthis.account = await getAccountById(this.id);\n\t\t\t\tthis.loading = false;\n\t\t\t}catch(e){\n\t\t\t\t//TODO handle the exception\n\t\t\t\tthis.loading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\tupdateAccount() {\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/account-form/account-form',\n\t\t\t\tsuccess: function(res) {\n\t\t\t\t\t$this.$store.commit('modelForm/setType', 2);\n\t\t\t\t\t$this.$store.commit('modelForm/setFlow', $this.account);\n\t\t\t    }\n\t\t\t});\n\t\t},\n\t\tcopyNo() {\n\t\t\tconsole.log(34);\n\t\t\tuni.setClipboardData({\n\t\t\t\tdata: this.account.no,\n\t\t\t\tsuccess: () => {\n\t\t\t\t\tuni.showToast({ //提示\n\t\t\t\t\t\ttitle: '复制成功'\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tadjustBalance() {\n\t\t\t\n\t\t}\n\t},\n\tasync onLoad(option) {\n\t\tthis.id = option.id;\n\t\tthis.refresh();\n\t\tvar $this = this;\n\t\tthis.$eventBus.$on('flowUpdated', function() {\n\t\t\t$this.refresh();\n\t\t});\n\t},\n}\n</script>\n\n<style>\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/accounts/accounts.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<u-navbar\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t:placeholder=\"true\"\n\t\t\t:border=\"true\"\n\t\t\t:fixed=\"true\"\n\t\t\tleftIcon=\"reload\"\n\t\t\tleftIconSize=\"25px\"\n\t\t\t@leftClick=\"refreshData\"\n\t\t>\n\t\t\t<view class=\"u-nav-slot\" slot=\"right\">\n\t\t\t\t<u-icon name=\"list\" size=\"25\" @click=\"showSortAction\"></u-icon>\n\t\t\t</view>\n\t\t</u-navbar>\n\t\t<u-tabs :list=\"tabList\" @click=\"tabClick\" :scrollable=\"false\"></u-tabs>\n\t\t<uni-list :border=\"true\">\n\t\t\t<uni-list-item v-for=\"(item, index) in accounts\" :key=\"index\" clickable\n\t\t\t\t:title=\"item.name\"\n\t\t\t\t:rightText=\"item.balanceFormatted\"\n\t\t\t\tlink @click=\"itemClick(item)\">\n\t\t\t</uni-list-item>\n\t\t</uni-list>\n\t\t<u-loadmore :status=\"status\" :loadmoreText=\"loadmoreText\" />\n\t\t<u-loading-page :loading=\"loading\"></u-loading-page>\n\t\t<tab-bar :selectedIndex=\"0\"></tab-bar>\n\t\t<u-action-sheet\n\t\t\t:actions=\"sortList\"\n\t\t\t:show=\"showSortList\"\n\t\t\t:closeOnClickOverlay=\"true\"\n\t\t\t@select=\"sortItemClick\"\n\t\t\t@close=\"closeSortAction\">\n\t\t</u-action-sheet>\n\t</view>\n</template>\n\n<script>\nimport { queryAccounts } from '@/config/api.js';\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\ttabList: [\n\t\t\t\t{ name: '活期' },\n\t\t\t\t{ name: '信用' },\n\t\t\t\t{ name: '贷款' },\n\t\t\t\t{ name: '资产' },\n\t\t\t],\n\t\t\tcurrentIndex: 0,\n\t\t\taccounts: [ ],\n\t\t\tqueryParams: {\n\t\t\t\tpage: 1,\n\t\t\t\tsize: 15,\n\t\t\t\tsort: '',\n\t\t\t},\n\t\t\tloading: false,\n\t\t\tstatus: 'loadmore',\n\t\t\tloadmoreText: \" \",\n\t\t\tshowSortList: false,\n\t\t\tsortList: [\n\t\t\t\t{\n\t\t\t\t\tname: '默认排序',\n\t\t\t\t\tvalue: ''\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '余额排序',\n\t\t\t\t\tvalue: 'balance,desc'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '可用优先',\n\t\t\t\t\tvalue: 'enable,desc'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '支出优先',\n\t\t\t\t\tvalue: 'expenseable,desc'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '收入优先',\n\t\t\t\t\tvalue: 'incomeable,desc'\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t},\n\tonLoad: async function() {\n\t\tif (!this.$store.getters.user) this.$store.dispatch('getSession');\n\t\tawait this.fetchData();\n\t},\n\tasync onReachBottom() {\n\t\tif (this.status === 'loadmore') {\n\t\t\tawait this.fetchData(true);\n\t\t}\n\t},\n\tasync onPullDownRefresh() {\n\t\tawait this.refreshData();\n\t\tuni.stopPullDownRefresh();\n\t},\n\tmethods: {\n\t\tasync refreshData() {\n\t\t\tthis.accounts = [];\n\t\t\tthis.queryParams.page = 1;\n\t\t\tthis.status = 'loadmore';\n\t\t\tawait this.fetchData();\n\t\t},\n\t\tasync fetchData(isMore) {\n\t\t\tif (!isMore) this.loading = true;\n\t\t\tif (isMore) this.status = 'loading';\n\t\t\ttry {\n\t\t\t\tconst response = await queryAccounts(this.currentIndex + 1, this.queryParams);\n\t\t\t\tif (response.content.length === 0) {\n\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t} else {\n\t\t\t\t\tthis.accounts = [...this.accounts, ...response.content];\n\t\t\t\t\tif (response.content.length < this.queryParams.size) {\n\t\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.queryParams.page = this.queryParams.page + 1;\n\t\t\t\t\t\tthis.status = 'loadmore';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.loading = false;\n\t\t\t} catch(e) {\n\t\t\t\tthis.loading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\ttabClick(item) {\n\t\t\tthis.currentIndex = item.index;\n\t\t\tthis.refreshData();\n\t\t},\n\t\titemClick(item) {\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/account-detail/account-detail?id=' + item.id,\n\t\t\t\tevents: {\n\t\t\t\t\tdeleteSuccess() {\n\t\t\t\t\t\t$this.refreshData();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tshowSortAction() {\n\t\t\tthis.showSortList = true;\n\t\t},\n\t\tcloseSortAction() {\n\t\t\tthis.showSortList = false;\n\t\t},\n\t\tsortItemClick(item) {\n\t\t\tthis.queryParams.sort = item.value;\n\t\t\tthis.showSortList = false;\n\t\t\tthis.refreshData();\n\t\t},\n\t}\n}\n</script>\n\n<style lang=\"scss\">\n.u-nav-slot {\n\t@include flex;\n\tmargin-right: 90px;\n\talign-items: center;\n\tjustify-content: space-between;\n\tborder-width: 0.5px;\n\tborder-radius: 100px;\n\tborder-color: $u-border-color;\n\tpadding: 3px 7px;\n\tgap: 10px;\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/books/books.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<uni-list :border=\"true\">\n\t\t\t<uni-list-item v-for=\"(item, index) in books\" :key=\"index\"\n\t\t\t\t:title=\"item.name\"\n\t\t\t\t:note=\"item.notes\">\n\t\t\t\t<template v-slot:footer>\n\t\t\t\t\t<u-button\n\t\t\t\t\t\t@click=\"setDefault(item)\"\n\t\t\t\t\t\ttext=\"设为默认\"\n\t\t\t\t\t\ttype=\"success\"\n\t\t\t\t\t\tsize=\"mini\"\n\t\t\t\t\t\t:disabled=\"defaultBook.id === item.id\"\n\t\t\t\t\t\t:customStyle=\"{width: 'auto'}\"\n\t\t\t\t\t></u-button>\n\t\t\t\t</template>\n\t\t\t</uni-list-item>\n\t\t</uni-list>\n\t\t<u-loadmore v-if=\"status !== 'empty'\" :status=\"status\" :loadmoreText=\"loadmoreText\" />\n\t\t<u-loading-page :loading=\"loading\"></u-loading-page>\n\t</view>\n</template>\n\n<script>\nimport { queryBooks, setDefaultBook } from '@/config/api.js';\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tbooks: [ ],\n\t\t\tqueryParams: {\n\t\t\t\tpage: 1,\n\t\t\t\tsize: 15,\n\t\t\t},\n\t\t\tloading: false,\n\t\t\tstatus: 'loadmore',\n\t\t\tloadmoreText: \" \",\n\t\t\tsetDefaultLoading: false\n\t\t}\n\t},\n\tcomputed: {\n\t\tdefaultBook() {\n\t\t\treturn this.$store.getters['defaultBook'];\n\t\t}\n\t},\n\tmounted: async function() {\n\t\tawait this.fetchData();\n\t},\n\tasync onReachBottom() {\n\t\tif (this.status === 'loadmore') {\n\t\t\tawait this.fetchData(true);\n\t\t}\n\t},\n\tasync onPullDownRefresh() {\n\t\tawait this.refreshData();\n\t\tuni.stopPullDownRefresh();\n\t},\n\tmethods: {\n\t\tasync refreshData() {\n\t\t\tthis.books = [];\n\t\t\tthis.queryParams = {\n\t\t\t\tpage: 1,\n\t\t\t\tsize: 15,\n\t\t\t};\n\t\t\tthis.status = 'loadmore';\n\t\t\tawait this.fetchData();\n\t\t},\n\t\tasync fetchData(isMore) {\n\t\t\tif (!isMore) this.loading = true;\n\t\t\tif (isMore) this.status = 'loading';\n\t\t\ttry {\n\t\t\t\tconst response = await queryBooks(this.queryParams);\n\t\t\t\tif (response.content.length === 0) {\n\t\t\t\t\tif (!isMore) {\n\t\t\t\t\t\tthis.status = 'empty';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.books = [...this.books, ...response.content];\n\t\t\t\t\tif (response.content.length < this.queryParams.size) {\n\t\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.queryParams.page = this.queryParams.page + 1;\n\t\t\t\t\t\tthis.status = 'loadmore';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.loading = false;\n\t\t\t} catch(e) {\n\t\t\t\tthis.loading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\tasync setDefault(item) {\n\t\t\tif (this.setDefaultLoading === true) return;\n\t\t\tthis.setDefaultLoading = true;\n\t\t\ttry {\n\t\t\t\tawait setDefaultBook(item.id);\n\t\t\t\tuni.$u.toast('操作成功');\n\t\t\t\tthis.setDefaultLoading = false;\n\t\t\t\tthis.$store.dispatch('getSession');\n\t\t\t\tawait this.refreshData();\n\t\t\t} catch(e) {\n\t\t\t\tthis.setDefaultLoading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t}\n\t},\n}\n</script>\n\n<style>\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/charts/charts.vue",
    "content": "<template>\n\t<view>\n\t\t<u-navbar\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t:placeholder=\"true\"\n\t\t\t:border=\"true\"\n\t\t\t:fixed=\"true\"\n\t\t\tleftIcon=\"reload\"\n\t\t\tleftIconSize=\"25px\"\n\t\t\t@leftClick=\"fetchData\"\n\t\t>\n\t\t\t<view class=\"u-nav-slot\" slot=\"right\">\n\t\t\t\t<u-icon name=\"search\" size=\"25\" @click=\"showSearchModal\"></u-icon>\n\t\t\t</view>\n\t\t</u-navbar>\n\t\t<u-tabs :list=\"tabList\" @click=\"tabClick\" :scrollable=\"false\" :customStyle=\"{marginBottom: '20px'}\"></u-tabs>\n\t\t<qiun-data-charts \n\t\t\ttype=\"ring\"\n\t\t\t:opts=\"opts\"\n\t\t\t:chartData=\"chartData\"\n\t\t\t:canvas2d=\"true\"\n\t\t/>\n\t\t<uni-list :border=\"true\" :customStyle=\"{marginTop: '20px'}\">\n\t\t\t<uni-list-item v-for=\"(item, index) in xyList\" :key=\"index\"\n\t\t\t\t:title=\"item.x\"\n\t\t\t\t:note=\"item.y.toFixed(2)\"\n\t\t\t\t:rightText=\"item.percent+'%'\"\n\t\t\t>\n\t\t\t</uni-list-item>\n\t\t</uni-list>\n\t\t<u-loading-page :loading=\"loading\"></u-loading-page>\n\t\t<tab-bar :selectedIndex=\"2\"></tab-bar>\n\t\t<u-popup\n\t\t\t:show=\"showSearchPopup\"\n\t\t\tmode=\"top\"\n\t\t\t:closeOnClickOverlay=\"true\"\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t@close=\"closeSearchPopup\"\n\t\t\t:customStyle=\"{background: '#fff'}\"\n\t\t>\n\t\t\t<view class=\"query-form\">\n\t\t\t\t<u--form :model=\"queryParams\" labelWidth=\"auto\">\n\t\t\t\t\t<u-form-item label=\"开始时间：\" borderBottom>\n\t\t\t\t\t\t<uni-datetime-picker v-model=\"queryParams.minTime\" type=\"date\" :border=\"false\" return-type=\"timestamp\" />\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"结束时间：\" borderBottom>\n\t\t\t\t\t\t<uni-datetime-picker v-model=\"queryParams.maxTime\" type=\"date\" :border=\"false\" return-type=\"timestamp\" />\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"分类：\" borderBottom @click=\"openCategorySelect\">\n\t\t\t\t\t\t<u--input v-model=\"queryParamsLabel.categoryName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择分类\" border=\"none\"></u--input>\n\t\t\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t</u--form>\n\t\t\t\t<u-button type=\"primary\" @click=\"querySubmit\" text=\"查询\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t\t\t<u-button @click=\"closeSearchPopup\" text=\"关闭\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t\t</view>\n\t\t</u-popup>\n\t</view>\n</template>\n\n<script>\nimport { reportExpenseCategory, reportIncomeCategory, reportDebt, reportAsset  } from '@/config/api.js';\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\ttabList: [\n\t\t\t\t{ name: '支出分类' },\n\t\t\t\t{ name: '收入分类' },\n\t\t\t\t{ name: '资产分类' },\n\t\t\t\t{ name: '负债分类' },\n\t\t\t],\n\t\t\tcurrentIndex: 0,\n\t\t\tloading: false,\n\t\t\txyList: [ ],\n\t\t\tqueryParams: {\n\t\t\t\tminTime: new Date(new Date().setDate(new Date().getDate() - 30)).getTime(),\n\t\t\t\tmaxTime: new Date().getTime(),\n\t\t\t\tcategoryId: undefined,\n\t\t\t},\n\t\t\tqueryParamsLabel: {\n\t\t\t\tcategoryName: undefined,\n\t\t\t},\n\t\t\tsearchDisable: false,\n\t\t\tshowSearchPopup: false,\n\t\t};\n\t},\n\tcomputed: {\n\t\tchartData() {\n\t\t\tconst seriesData = this.xyList.map(e => {\n\t\t\t\treturn {\n\t\t\t\t\t\"name\": e.x,\n\t\t\t\t\t\"value\": e.y\n\t\t\t\t}\n\t\t\t});\n\t\t\tconst chartData = {\n\t\t\t\tseries: [\n\t\t\t\t  {\n\t\t\t\t\tdata: seriesData\n\t\t\t\t  }\n\t\t\t\t]\n\t\t\t};\n\t\t\treturn JSON.parse(JSON.stringify(chartData));\n\t\t},\n\t\topts() {\n\t\t\tlet totalAmount = 0;\n\t\t\tthis.xyList.forEach(i => {\n\t\t\t\ttotalAmount += i.y*100;\n\t\t\t});\n\t\t\ttotalAmount = (totalAmount / 100).toFixed(2);\n\t\t\treturn {\n\t\t\t\tlegend: {\n\t\t\t\t\tposition: 'bottom',\n\t\t\t\t\tmargin: 10,\n\t\t\t\t},\n\t\t\t\ttitle: {\n\t\t\t\t\tname: \"总金额\",\n\t\t\t\t\tfontSize: 12,\n\t\t\t\t\tcolor: \"#666666\"\n\t\t\t\t},\n\t\t\t\tsubtitle: {\n\t\t\t\t\tname: totalAmount,\n\t\t\t\t\tfontSize: 18,\n\t\t\t\t\tcolor: \"#7cb5ec\"\n\t\t\t\t},\n\t\t\t\textra: {\n\t\t\t\t\tring: {\n\t\t\t\t\t\tringWidth: 20\n\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\tonLoad: async function() {\n\t\tif (!this.$store.getters.expenseCategories) this.$store.dispatch('getExpenseCategories');\n\t\tif (!this.$store.getters.incomeCategories) this.$store.dispatch('getIncomeCategories');\n\t\tawait this.fetchData();\n\t},\n\tmethods: {\n\t\tasync tabClick(item) {\n\t\t\tthis.currentIndex = item.index;\n\t\t\tthis.queryReset();\n\t\t\tif (this.currentIndex === 2 || this.currentIndex === 3) {\n\t\t\t\tthis.searchDisable = true;\n\t\t\t} else {\n\t\t\t\tthis.searchDisable = false;\n\t\t\t}\n\t\t\tawait this.fetchData();\n\t\t},\n\t\tasync fetchData() {\n\t\t\ttry {\n\t\t\t\tthis.loading = true;\n\t\t\t\tif (this.currentIndex === 0) {\n\t\t\t\t\tthis.xyList = await reportExpenseCategory(this.queryParams);\n\t\t\t\t} else if (this.currentIndex === 1) {\n\t\t\t\t\tthis.xyList = await reportIncomeCategory(this.queryParams);\n\t\t\t\t} else if (this.currentIndex === 2) {\n\t\t\t\t\tthis.xyList = await reportAsset();\n\t\t\t\t} else if (this.currentIndex === 3) {\n\t\t\t\t\tthis.xyList = await reportDebt();\n\t\t\t\t}\n\t\t\t\tthis.loading = false;\n\t\t\t} catch(e) {\n\t\t\t\tthis.loading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\tshowSearchModal() {\n\t\t\tif (this.searchDisable === true) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.showSearchPopup = true;\n\t\t},\n\t\tcloseSearchPopup() {\n\t\t\tthis.showSearchPopup = false;\n\t\t},\n\t\topenCategorySelect() {\n\t\t\tif (this.currentIndex === 0) {\n\t\t\t\tthis.$store.commit('setSelectList', this.$store.getters.expenseCategories);\n\t\t\t} else {\n\t\t\t\tthis.$store.commit('setSelectList', this.$store.getters.incomeCategories);\n\t\t\t}\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择分类&value=' + this.queryParams.categoryId,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\t$this.queryParams.categoryId = n;\n\t\t\t\t\t\tif ($this.currentIndex === 0) {\n\t\t\t\t\t\t\t$this.queryParamsLabel.categoryName = $this.$store.getters.expenseCategories.find(e => e.id === n).name;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t$this.queryParamsLabel.categoryName = $this.$store.getters.incomeCategories.find(e => e.id === n).name;\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\tasync querySubmit() {\n\t\t\tthis.closeSearchPopup();\n\t\t\tawait this.fetchData();\n\t\t},\n\t\tqueryReset() {\n\t\t\tthis.queryParams.minTime = new Date(new Date().setDate(new Date().getDate() - 30)).getTime();\n\t\t\tthis.queryParams.maxTime = new Date().getTime();\n\t\t\tthis.queryParams.categoryId = undefined;\n\t\t\tthis.queryParamsLabel.categoryName = undefined;\n\t\t}\n\t}\n};\n</script>\n\n\n<style lang=\"scss\">\n.u-nav-slot {\n\t@include flex;\n\tmargin-right: 90px;\n\talign-items: center;\n\tjustify-content: space-between;\n\tborder-width: 0.5px;\n\tborder-radius: 100px;\n\tborder-color: $u-border-color;\n\tpadding: 3px 7px;\n\tgap: 10px;\n}\n.query-form {\n\tpadding: 30px 10px;\n}\n</style>"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/flow-detail/flow-detail.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<view v-if=\"!loading\" class=\"u-content\">\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\"></u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">\n\t\t\t\t<u-button type=\"primary\" text=\"复制\" size=\"mini\" customStyle=\"margin-right: 5px\" @click=\"copyFlow\"></u-button>\n\t\t\t\t<u-button type=\"primary\" text=\"修改\" size=\"mini\" customStyle=\"margin-right: 5px\" @click=\"updateFlow\"></u-button>\n\t\t\t\t<u-button type=\"primary\" :disabled=\"refundDisabled\" text=\"退款\" size=\"mini\" customStyle=\"margin-right: 5px\" @click=\"refundFlow\"></u-button>\n\t\t\t\t<u-button type=\"primary\" :disabled=\"flow.status != 2\" text=\"确认\" size=\"mini\" customStyle=\"margin-right: 5px\" @click=\"showConfirmModal = true\"></u-button>\n\t\t\t\t<u-button type=\"primary\" text=\"图片\" size=\"mini\" customStyle=\"margin-right: 5px\"></u-button>\n\t\t\t\t<u-button type=\"primary\" text=\"删除\" size=\"mini\" @click=\"showDeleteModal = true\"></u-button>\n\t\t\t</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">描述：{{flow.description}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">交易类型：{{flow.typeName}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">时间：{{flow.createTimeFormatted}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">金额：{{flow.amount}}</u-row>\n\t\t\t<u-row v-if=\"flow.needConvert\" customStyle=\"margin-bottom: 10px\">折合{{flow.toCurrencyCode}}：{{flow.convertedAmount}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">账户：{{flow.accountName}}</u-row>\n\t\t\t<u-row v-if=\"flow.type === 1 || flow.type === 2\" customStyle=\"margin-bottom: 10px\">类别：{{flow.categoryName}}</u-row>\n\t\t\t<u-row v-if=\"flow.type !== 4\" customStyle=\"margin-bottom: 10px\">标签：{{flow.tagsName}}</u-row>\n\t\t\t<u-row v-if=\"flow.type === 1 || flow.type === 2\" customStyle=\"margin-bottom: 10px\">交易对象：{{flow.payee ? flow.payee.name : ''}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">状态：{{flow.statusName}}</u-row>\n\t\t\t<u-row customStyle=\"margin-bottom: 10px\">备注：{{flow.notes}}</u-row>\n\t\t</view>\n\t\t<u-loading-page :loading=\"loading\"></u-loading-page>\n\t\t<u-modal\n\t\t\tcontent=\"删除账单会撤回对应账户余额变动且无法恢复，确认此操作吗？\"\n\t\t\t:show=\"showDeleteModal\"\n\t\t\tshowCancelButton\n\t\t\tcloseOnClickOverlay\n\t\t\t@confirm=\"deleteHandler\"\n\t\t\t@cancel=\"showDeleteModal = false\"\n\t\t\t@close=\"showDeleteModal = false\"\n\t\t></u-modal>\n\t\t<u-modal\n\t\t\tcontent=\"将更改对应账号余额，确认此操作吗？\"\n\t\t\t:show=\"showConfirmModal\"\n\t\t\tshowCancelButton\n\t\t\tcloseOnClickOverlay\n\t\t\t@confirm=\"confirmHandler\"\n\t\t\t@cancel=\"showConfirmModal = false\"\n\t\t\t@close=\"showConfirmModal = false\"\n\t\t></u-modal>\n\t</view>\n</template>\n\n<script>\nimport { getFlowById, deleteFlowById, confirmFlow } from '@/config/api.js';\n\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tid: undefined,\n\t\t\tflow: undefined,\n\t\t\tloading: true,\n\t\t\tshowDeleteModal: false,\n\t\t\tdeleteLoading: false,\n\t\t\tshowConfirmModal: false,\n\t\t\tconfirmLoading: false,\n\t\t}\n\t},\n\tcomputed: {\n\t\trefundDisabled() {\n\t\t\treturn !((this.flow.type === 1 || this.flow.type === 2) && (this.flow.status !== 2 && this.flow.amount > 0));\n\t\t},\n\t\tdefaultCurrencyCode() {\n\t\t\treturn this.$store.getters.defaultCurrencyCode;\n\t\t}\n\t},\n\tmethods: {\n\t\tasync deleteHandler() {\n\t\t\tthis.deleteLoading = true;\n\t\t\ttry {\n\t\t\t\tawait deleteFlowById(this.flow.id);\n\t\t\t\tuni.$u.toast('操作成功');\n\t\t\t\tconst eventChannel = this.getOpenerEventChannel();\n\t\t\t\teventChannel.emit('deleteSuccess');\n\t\t\t\tuni.navigateBack();\n\t\t\t\tthis.deleteLoading = false;\n\t\t\t} catch(e) {\n\t\t\t\tthis.deleteLoading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\tasync confirmHandler() {\n\t\t\tthis.confirmLoading = true;\n\t\t\ttry {\n\t\t\t\tawait confirmFlow(this.flow.id);\n\t\t\t\tthis.showConfirmModal = false;\n\t\t\t\tuni.$u.toast('操作成功');\n\t\t\t\tthis.confirmLoading = false;\n\t\t\t\tthis.refresh();\n\t\t\t} catch(e) {\n\t\t\t\tthis.confirmLoading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\tasync refresh() {\n\t\t\tthis.loading = true;\n\t\t\ttry{\n\t\t\t\tthis.flow = await getFlowById(this.id);\n\t\t\t\tthis.loading = false;\n\t\t\t}catch(e){\n\t\t\t\t//TODO handle the exception\n\t\t\t\tthis.loading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\tcopyFlow() {\n\t\t\tthis.$store.commit('modelForm/setType', 3);\n\t\t\tthis.$store.commit('modelForm/setModel', this.flow);\n\t\t\tuni.navigateTo({ url: '/pages/flow-form/flow-form' });\n\t\t},\n\t\tupdateFlow() {\n\t\t\tthis.$store.commit('modelForm/setType', 2);\n\t\t\tthis.$store.commit('modelForm/setModel', this.flow);\n\t\t\tuni.navigateTo({ url: '/pages/flow-form/flow-form' });\n\t\t},\n\t\trefundFlow() {\n\t\t\tthis.$store.commit('modelForm/setType', 4);\n\t\t\tthis.$store.commit('modelForm/setModel', this.flow);\n\t\t\tuni.navigateTo({ url: '/pages/flow-form/flow-form' });\n\t\t}\n\t},\n\tasync onLoad(option) {\n\t\tthis.id = option.id;\n\t\tthis.refresh();\n\t\tvar $this = this;\n\t\tthis.$eventBus.$on('flowUpdated', function() {\n\t\t\t$this.refresh();\n\t\t});\n\t},\n}\n</script>\n\n<style>\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/flow-form/flow-form.vue",
    "content": "<template>\n\t<view>\n\t\t<u-tabs v-if=\"formType === 1\" :list=\"tabList\" @click=\"tabClick\" :scrollable=\"false\"></u-tabs>\n\t\t<expense-form v-if=\"tabIndex === 0\"></expense-form>\n\t\t<income-form v-else-if=\"tabIndex === 1\"></income-form>\n\t\t<transfer-form v-else-if=\"tabIndex === 2\"></transfer-form>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\ttabList: [\n\t\t\t\t\t{ name: '支出' },\n\t\t\t\t\t{ name: '收入' },\n\t\t\t\t\t{ name: '转账' },\n\t\t\t\t],\n\t\t\t\tcurrentIndex: 0,\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\ttabClick(item) {\n\t\t\t\tthis.currentIndex = item.index;\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\ttabIndex() {\n\t\t\t\tconst formType = this.$store.getters['modelForm/type'];\n\t\t\t\tconst flowType = this.$store.getters['modelForm/model'].type;\n\t\t\t\tif (formType === 1) {\n\t\t\t\t\treturn this.currentIndex;\n\t\t\t\t} else {\n\t\t\t\t\treturn flowType - 1;\n\t\t\t\t}\n\t\t\t},\n\t\t\tformType() {\n\t\t\t\treturn this.$store.getters['modelForm/type'];\n\t\t\t},\n\t\t\ttitle() {\n\t\t\t\tconst formType = this.$store.getters['modelForm/type'];\n\t\t\t\tconst flowType = this.$store.getters['modelForm/model'].type;\n\t\t\t\tif (formType === 1) {\n\t\t\t\t\treturn \"新增流水\";\n\t\t\t\t} else if (formType === 2) {\n\t\t\t\t\tif (flowType === 1) {\n\t\t\t\t\t\treturn \"修改支出\";\n\t\t\t\t\t} else if (flowType === 2) {\n\t\t\t\t\t\treturn \"修改收入\";\n\t\t\t\t\t} else if (flowType === 3) {\n\t\t\t\t\t\treturn \"修改转账\";\n\t\t\t\t\t}\n\t\t\t\t} else if (formType === 3) {\n\t\t\t\t\tif (flowType === 1) {\n\t\t\t\t\t\treturn \"复制支出\";\n\t\t\t\t\t} else if (flowType === 2) {\n\t\t\t\t\t\treturn \"复制收入\";\n\t\t\t\t\t} else if (flowType === 3) {\n\t\t\t\t\t\treturn \"复制转账\";\n\t\t\t\t\t}\n\t\t\t\t} else if (formType === 4) {\n\t\t\t\t\tif (flowType === 1) {\n\t\t\t\t\t\treturn \"退款支出\";\n\t\t\t\t\t} else if (flowType === 2) {\n\t\t\t\t\t\treturn \"退款收入\";\n\t\t\t\t\t} else if (flowType === 3) {\n\t\t\t\t\t\treturn \"退款转账\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tonLoad(option) {\n\t\t\tuni.setNavigationBarTitle({ title: this.title });\n\t\t},\n\t}\n</script>\n\n<style>\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/flows/flows.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<u-navbar\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t:placeholder=\"true\"\n\t\t\t:border=\"true\"\n\t\t\t:fixed=\"true\"\n\t\t\tleftIcon=\"reload\"\n\t\t\tleftIconSize=\"25px\"\n\t\t\t@leftClick=\"refreshData\"\n\t\t>\n\t\t\t<view class=\"u-nav-slot\" slot=\"right\">\n\t\t\t\t<u-icon name=\"list\" size=\"25\" @click=\"showSortAction\"></u-icon>\n\t\t\t\t<u-icon name=\"search\" size=\"25\" @click=\"showSearchModal\"></u-icon>\n\t\t\t</view>\n\t\t</u-navbar>\n\t\t<uni-list :border=\"false\">\n\t\t\t<uni-list-item v-for=\"(item, index) in flows\" :key=\"index\" clickable\n\t\t\t\t:class=\"{'expense': item.type === 1, 'income': item.type === 2}\"\n\t\t\t\t:title=\"item.title\"\n\t\t\t\t:rightText=\"item.amountFormatted\"\n\t\t\t\t:note=\"item.subTitle\"\n\t\t\t\tlink @click=\"itemClick(item)\">\n\t\t\t</uni-list-item>\n\t\t</uni-list>\n\t\t<u-loadmore v-if=\"status !== 'empty'\" :status=\"status\" :loadmoreText=\"loadmoreText\" />\n\t\t<u-loading-page :loading=\"loading\"></u-loading-page>\n\t\t<u-empty mode=\"search\" v-if=\"status === 'empty'\"></u-empty>\n\t\t<tab-bar :selectedIndex=\"1\"></tab-bar>\n\t\t<u-action-sheet\n\t\t\t:actions=\"sortList\"\n\t\t\t:show=\"showSortList\"\n\t\t\t:closeOnClickOverlay=\"true\"\n\t\t\t@select=\"sortItemClick\"\n\t\t\t@close=\"closeSortAction\">\n\t\t</u-action-sheet>\n\t\t<u-popup\n\t\t\t:show=\"showSearchPopup\"\n\t\t\tmode=\"top\"\n\t\t\t:closeOnClickOverlay=\"true\"\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t@close=\"closeSearchPopup\"\n\t\t>\n\t\t\t<view class=\"query-form\">\n\t\t\t\t<u--form :model=\"queryParams\" labelWidth=\"auto\">\n\t\t\t\t\t<u-form-item label=\"描述：\" borderBottom>\n\t\t\t\t\t\t<u--input v-model=\"queryParams.description\" border=\"none\"></u--input>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"开始时间：\" borderBottom>\n\t\t\t\t\t\t<uni-datetime-picker v-model=\"queryParams.minTime\" type=\"date\" :border=\"false\" return-type=\"timestamp\" />\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"结束时间：\" borderBottom>\n\t\t\t\t\t\t<uni-datetime-picker v-model=\"queryParams.maxTime\" type=\"date\" :border=\"false\" return-type=\"timestamp\" />\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"交易对象：\" borderBottom @click=\"openPayeeSelect\">\n\t\t\t\t\t\t<u--input v-model=\"queryParamsLabel.payeeName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择交易对象\" border=\"none\"></u--input>\n\t\t\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"交易标签：\" borderBottom @click=\"openTagSelect\">\n\t\t\t\t\t\t<u--input v-model=\"queryParamsLabel.tagName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择标签\" border=\"none\"></u--input>\n\t\t\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"支出分类：\" borderBottom @click=\"openExpenseCategorySelect\">\n\t\t\t\t\t\t<u--input v-model=\"queryParamsLabel.expenseCategoryName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择支出分类\" border=\"none\"></u--input>\n\t\t\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"收入分类：\" borderBottom @click=\"openIncomeCategorySelect\">\n\t\t\t\t\t\t<u--input v-model=\"queryParamsLabel.incomeCategoryName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择收入分类\" border=\"none\"></u--input>\n\t\t\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"交易类型：\" borderBottom @click=\"openFlowTypeSelect\">\n\t\t\t\t\t\t<u--input v-model=\"queryParamsLabel.typeName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择类型\" border=\"none\"></u--input>\n\t\t\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"交易账户：\" borderBottom @click=\"openAccountSelect\">\n\t\t\t\t\t\t<u--input v-model=\"queryParamsLabel.accountName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择账户\" border=\"none\"></u--input>\n\t\t\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t\t<u-form-item label=\"交易状态：\" borderBottom @click=\"openFlowStatusSelect\">\n\t\t\t\t\t\t<u--input v-model=\"queryParamsLabel.statusName\" disabled disabledColor=\"#ffffff\" placeholder=\"请选择状态\" border=\"none\"></u--input>\n\t\t\t\t\t\t<u-icon slot=\"right\" name=\"arrow-right\"></u-icon>\n\t\t\t\t\t</u-form-item>\n\t\t\t\t</u--form>\n\t\t\t\t<u-button type=\"primary\" @click=\"queryFlowsSubmit\" text=\"查询\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t\t\t<u-button type=\"primary\" @click=\"queryFlowsReset\" text=\"重置\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t\t\t<u-button @click=\"closeSearchPopup\" text=\"关闭\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t\t\t<u-picker\n\t\t\t\t\t:show=\"flowTypeSearchShow\"\n\t\t\t\t\t:columns=\"flowTypes\"\n\t\t\t\t\tkeyName=\"label\"\n\t\t\t\t\t@confirm=\"confirmFlowType\"\n\t\t\t\t\t@close=\"closeFlowType\"\n\t\t\t\t\t@cancel=\"closeFlowType\"\n\t\t\t\t\t:closeOnClickOverlay=true\n\t\t\t\t></u-picker>\n\t\t\t\t<u-picker\n\t\t\t\t\t:show=\"statusSearchShow\"\n\t\t\t\t\t:columns=\"flowStatus\"\n\t\t\t\t\tkeyName=\"label\"\n\t\t\t\t\t@confirm=\"confirmFlowStatus\"\n\t\t\t\t\t@close=\"closeFlowStatus\"\n\t\t\t\t\t@cancel=\"closeFlowStatus\"\n\t\t\t\t\t:closeOnClickOverlay=true\n\t\t\t\t></u-picker>\n\t\t\t</view>\n\t\t</u-popup>\n\t</view>\n</template>\n\n<script>\nimport { queryFlows } from '@/config/api.js';\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\tflows: [ ],\n\t\t\tqueryParams: {\n\t\t\t\tdescription: undefined,\n\t\t\t\tminTime: undefined,\n\t\t\t\tmaxTime: undefined,\n\t\t\t\tpayees: undefined,\n\t\t\t\ttags: undefined,\n\t\t\t\tcategories: undefined,\n\t\t\t\ttype: undefined,\n\t\t\t\taccountId: undefined,\n\t\t\t\tstatus: undefined,\n\t\t\t\tpage: 1,\n\t\t\t\tsize: 15,\n\t\t\t\tsort: 'createTime,desc',\n\t\t\t},\n\t\t\tqueryParamsLabel: {\n\t\t\t\tpayeeName: undefined,\n\t\t\t\ttagName: undefined,\n\t\t\t\texpenseCategoryName: undefined,\n\t\t\t\tincomeCategoryName: undefined,\n\t\t\t\ttypeName: undefined,\n\t\t\t\taccountName: undefined,\n\t\t\t\tstatusName: undefined,\n\t\t\t},\n\t\t\tloading: false,\n\t\t\tstatus: 'loadmore',\n\t\t\tloadmoreText: \" \",\n\t\t\tshowSortList: false,\n\t\t\tsortList: [\n\t\t\t\t{\n\t\t\t\t\tname: '按时间排序',\n\t\t\t\t\tvalue: 'createTime,desc'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '按金额排序',\n\t\t\t\t\tvalue: 'amount,desc'\n\t\t\t\t}\n\t\t\t],\n\t\t\tshowSearchPopup: false,\n\t\t\tflowTypeSearchShow: false,\n\t\t\tflowTypes: [\n\t\t\t\t[\n\t\t\t\t\t{\n\t\t\t\t\t\tid: 1,\n\t\t\t\t\t\tlabel: '支出'\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: 2,\n\t\t\t\t\t\tlabel: '收入'\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: 3,\n\t\t\t\t\t\tlabel: '转账'\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: 4,\n\t\t\t\t\t\tlabel: '余额调整'\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t],\n\t\t\tstatusSearchShow: false,\n\t\t\tflowStatus: [\n\t\t\t\t[\n\t\t\t\t\t{\n\t\t\t\t\t\tid: 1,\n\t\t\t\t\t\tlabel: '正常'\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: 2,\n\t\t\t\t\t\tlabel: '待确认'\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: 3,\n\t\t\t\t\t\tlabel: '已退款'\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t]\n\t\t}\n\t},\n\tonLoad: async function() {\n\t\tif (!this.$store.getters.user) this.$store.dispatch('getSession');\n\t\tif (!this.$store.getters.enablePayees) this.$store.dispatch('getEnablePayees');\n\t\tif (!this.$store.getters.enableTags) this.$store.dispatch('getEnableTags');\n\t\tif (!this.$store.getters.expenseCategories) this.$store.dispatch('getExpenseCategories');\n\t\tif (!this.$store.getters.incomeCategories) this.$store.dispatch('getIncomeCategories');\n\t\tif (!this.$store.getters.enableAccounts) this.$store.dispatch('getEnableAccounts');\n\t\t\n\t\tawait this.fetchData();\n\t\t\n\t\tvar $this = this;\n\t\tthis.$eventBus.$on('flowsUpdated', function() {\n\t\t\t$this.refreshData();\n\t\t});\n\t\t\n\t},\n\tasync onReachBottom() {\n\t\tif (this.status === 'loadmore') {\n\t\t\tawait this.fetchData(true);\n\t\t}\n\t},\n\tasync onPullDownRefresh() {\n\t\tawait this.refreshData();\n\t\tuni.stopPullDownRefresh();\n\t},\n\tmethods: {\n\t\tasync refreshData() {\n\t\t\tthis.flows = [];\n\t\t\tthis.queryParams = {\n\t\t\t\tpage: 1,\n\t\t\t\tsize: 15,\n\t\t\t\tsort: 'createTime,desc',\n\t\t\t};\n\t\t\tthis.queryParamsLabel = { },\n\t\t\tthis.status = 'loadmore';\n\t\t\tawait this.fetchData();\n\t\t},\n\t\tasync fetchData(isMore) {\n\t\t\tif (!isMore) this.loading = true;\n\t\t\tif (isMore) this.status = 'loading';\n\t\t\ttry{\n\t\t\t\tconst response = await queryFlows(this.queryParams);\n\t\t\t\tif (response.result.content.length === 0) {\n\t\t\t\t\tif (!isMore) {\n\t\t\t\t\t\tthis.status = 'empty';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.flows = [...this.flows, ...response.result.content];\n\t\t\t\t\tif (response.result.content.length < this.queryParams.size) {\n\t\t\t\t\t\tthis.status = 'nomore';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.queryParams.page = this.queryParams.page + 1;\n\t\t\t\t\t\tthis.status = 'loadmore';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.loading = false;\n\t\t\t} catch(e) {\n\t\t\t\tthis.loading = false;\n\t\t\t\tconsole.log(e);\n\t\t\t}\n\t\t},\n\t\titemClick(item) {\n\t\t\tvar $this = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/flow-detail/flow-detail?id=' + item.id,\n\t\t\t\tevents: {\n\t\t\t\t\tdeleteSuccess() {\n\t\t\t\t\t\t$this.refreshData();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tshowSortAction() {\n\t\t\tthis.showSortList = true;\n\t\t},\n\t\tcloseSortAction() {\n\t\t\tthis.showSortList = false;\n\t\t},\n\t\tsortItemClick(item) {\n\t\t\tthis.queryParams.sort = item.value;\n\t\t\tthis.showSortList = false;\n\t\t\tthis.refreshData();\n\t\t},\n\t\tshowSearchModal() {\n\t\t\tthis.showSearchPopup = true;\n\t\t},\n\t\tcloseSearchPopup() {\n\t\t\tthis.showSearchPopup = false;\n\t\t},\n\t\topenPayeeSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.enablePayees);\n\t\t\tvar that = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择交易对象&value=' + this.queryParams.payees,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tthat.queryParams.payees = n;\n\t\t\t\t\t\tthat.queryParamsLabel.payeeName = that.$store.getters.enablePayees.find(e => e.id === n).name;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenTagSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.enableTags);\n\t\t\tvar that = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择标签&value=' + this.queryParams.tags,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tthat.queryParams.tags = n;\n\t\t\t\t\t\tthat.queryParamsLabel.tagName = that.$store.getters.enableTags.find(e => e.id === n).name;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenExpenseCategorySelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.expenseCategories);\n\t\t\tvar that = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择支出分类&value=' + this.queryParams.categories,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tthat.queryParams.categories = n;\n\t\t\t\t\t\tthat.queryParamsLabel.expenseCategoryName = that.$store.getters.expenseCategories.find(e => e.id === n).name;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenIncomeCategorySelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.incomeCategories);\n\t\t\tvar that = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择收入分类&value=' + this.queryParams.categories,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tthat.queryParams.categories = n;\n\t\t\t\t\t\tthat.queryParamsLabel.incomeCategoryName = that.$store.getters.incomeCategories.find(e => e.id === n).name;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\topenFlowTypeSelect() {\n\t\t\tthis.flowTypeSearchShow = true;\n\t\t},\n\t\topenAccountSelect() {\n\t\t\tthis.$store.commit('setSelectList', this.$store.getters.enableAccounts);\n\t\t\tvar that = this;\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/select/select?title=选择账户&value=' + this.queryParams.accountId,\n\t\t\t\tevents: {\n\t\t\t\t\tselectedValue(n) {\n\t\t\t\t\t\tthat.queryParams.accountId = n;\n\t\t\t\t\t\tthat.queryParamsLabel.accountName = that.$store.getters.enableAccounts.find(e => e.id === n).name;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\tconfirmFlowType(e) {\n\t\t\tthis.queryParams.type = e.value[0].id;\n\t\t\tthis.queryParamsLabel.typeName = e.value[0].label;\n\t\t\tthis.flowTypeSearchShow = false;\n\t\t},\n\t\tcloseFlowType() {\n\t\t\tthis.flowTypeSearchShow = false;\n\t\t},\n\t\topenFlowStatusSelect() {\n\t\t\tthis.statusSearchShow = true;\n\t\t},\n\t\tconfirmFlowStatus(e) {\n\t\t\tthis.queryParams.status = e.value[0].id;\n\t\t\tthis.queryParamsLabel.statusName = e.value[0].label;\n\t\t\tthis.statusSearchShow = false;\n\t\t},\n\t\tcloseFlowStatus() {\n\t\t\tthis.statusSearchShow = false;\n\t\t},\n\t\tasync queryFlowsSubmit() {\n\t\t\tthis.closeSearchPopup();\n\t\t\tthis.flows = [];\n\t\t\tthis.queryParams.page = 1;\n\t\t\tthis.queryParams.size = 15;\n\t\t\tthis.status = 'loadmore';\n\t\t\tawait this.fetchData();\n\t\t},\n\t\tqueryFlowsReset() {\n\t\t\tthis.queryParams.description = undefined;\n\t\t\tthis.queryParams.minTime = undefined;\n\t\t\tthis.queryParams.maxTime = undefined;\n\t\t\tthis.queryParams.payees = undefined;\n\t\t\tthis.queryParams.tags = undefined;\n\t\t\tthis.queryParams.categories = undefined;\n\t\t\tthis.queryParams.type = undefined;\n\t\t\tthis.queryParamsLabel = { };\n\t\t}\n\t}\n}\n</script>\n\n<style lang=\"scss\">\n.u-nav-slot {\n\t@include flex;\n\tmargin-right: 90px;\n\talign-items: center;\n\tjustify-content: space-between;\n\tborder-width: 0.5px;\n\tborder-radius: 100px;\n\tborder-color: $u-border-color;\n\tpadding: 3px 7px;\n\tgap: 10px;\n}\n.query-form {\n\tpadding: 30px 10px;\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/index/index.vue",
    "content": "<template>\n\t<view>\n\t\t<u-loading-page :loading=\"true\"></u-loading-page>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t\n\t\t\t}\n\t\t},\n\t\tonLoad: function() {\n\t\t\tuni.$u.http.setConfig((config) => {\n\t\t\t\tconfig.baseURL = uni.getStorageSync('apiUrl');\n\t\t\t\treturn config;\n\t\t\t});\n\t\t\tif (uni.getStorageSync('userToken')) {\n\t\t\t\tuni.reLaunch({ url: '/pages/flows/flows' });\n\t\t\t} else {\n\t\t\t\tuni.reLaunch({ url: '/pages/login/login' });\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\">\n\t\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/login/login.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<image class=\"logo\" src=\"/static/logo.png\"></image>\n\t\t<u--form labelWidth=\"60\" :model=\"form\" :rules=\"rules\" ref=\"uForm\">\n\t\t\t<u-form-item label=\"用户名\" prop=\"userName\">\n\t\t\t\t<u--input v-model=\"form.userName\"></u--input>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"密码\" prop=\"password\">\n\t\t\t\t<u--input type=\"password\" v-model=\"form.password\"></u--input>\n\t\t\t</u-form-item>\n\t\t\t<u-form-item label=\"API地址\" prop=\"apiUrl\">\n\t\t\t\t<u--input v-model=\"form.apiUrl\"></u--input>\n\t\t\t</u-form-item>\n\t\t</u--form>\n\t\t<u-button type=\"primary\" @click=\"submit\" :loading=\"loading\" text=\"提交\"></u-button>\n\t</view>\n</template>\n\n<script>\n\timport { signin } from '@/config/api.js';\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tform: {\n\t\t\t\t\tuserName: '',\n\t\t\t\t\tpassword: '',\n\t\t\t\t\tapiUrl: 'https://testjz.jiukuaitech.com/api/v1/'\n\t\t\t\t},\n\t\t\t\tloading: false,\n\t\t\t\trules: {\n\t\t\t\t\t'userName': {\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tmessage: '请输入用户名',\n\t\t\t\t\t\ttrigger: ['blur', 'change']\n\t\t\t\t\t},\n\t\t\t\t\t'password': {\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tmessage: '请输入密码',\n\t\t\t\t\t\ttrigger: ['blur', 'change']\n\t\t\t\t\t},\n\t\t\t\t\t'apiUrl': {\n\t\t\t\t\t\ttype: 'string',\n\t\t\t\t\t\trequired: true,\n\t\t\t\t\t\tmessage: '请输入API地址',\n\t\t\t\t\t\ttrigger: ['blur', 'change']\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t\n\t\t},\n\t\tmethods: {\n\t\t\tasync submit() {\n\t\t\t\ttry {\n\t\t\t\t\tthis.loading = true;\n\t\t\t\t\tawait this.$refs.uForm.validate();\n\t\t\t\t\tconst $this = this;\n\t\t\t\t\tuni.$u.http.setConfig((config) => {\n\t\t\t\t\t\tconfig.baseURL = $this.form.apiUrl;\n\t\t\t\t\t\treturn config;\n\t\t\t\t\t});\n\t\t\t\t\tthis.$store.commit('updateApiUrl', $this.form.apiUrl);\n\t\t\t\t\tconst data = await signin(this.form);\n\t\t\t\t\tthis.$store.commit('updateToken', data.token);\n\t\t\t\t\tuni.$u.toast('登录成功');\n\t\t\t\t\tthis.loading = false;\n\t\t\t\t\tuni.redirectTo({ url: '/pages/flows/flows' });\n\t\t\t\t} catch (e) {\n\t\t\t\t\tthis.loading = false;\n\t\t\t\t\tconsole.log(e);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tonReady() {\n\t\t\t// 如果需要兼容微信小程序，并且校验规则中含有方法等，只能通过setRules方法设置规则\n\t\t\tthis.$refs.uForm.setRules(this.rules)\n\t\t},\n\t}\n</script>\n\n<style>\n\t.u-page {\n\t\tpadding: 0 15px;\n\t}\n\t.logo {\n\t\tdisplay: block;\n\t\tmargin: 200rpx auto 50rpx auto;\n\t\theight: 200rpx;\n\t\twidth: 200rpx;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/my/my.vue",
    "content": "<template>\n\t<view class=\"u-page\">\n\t\t<u-navbar\n\t\t\ttitle=\"我的\"\n\t\t\tleftIcon=\" \"\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t:placeholder=\"true\"\n\t\t\t:border=\"true\"\n\t\t\t:fixed=\"true\">\n\t\t</u-navbar>\n\t\t<uni-list>\n\t\t\t<uni-list-item title=\"用户名\" :rightText=\"user && user.userName\" />\n\t\t\t<uni-list-item title=\"会员到期日\" :rightText=\"user && vipTime\" />\n\t\t\t<uni-list-item title=\"当前组\" :rightText=\"defaultGroup && defaultGroup.name\" />\n\t\t\t<uni-list-item title=\"当前账本\" :rightText=\"defaultBook && defaultBook.name\" />\n\t\t</uni-list>\n\t\t\n\t\t<uni-list :border=\"false\" style=\"margin-top: 20px;\">\n\t\t\t<uni-list-item title=\"账本管理\" clickable link @click=\"openBooks\" />\n\t\t\t<uni-list-item title=\"API地址\" :rightText=\"apiUrl\" />\n\t\t\t<uni-list-item title=\"当前版本号\" rightText=\"1.0.2\" />\n\t\t</uni-list>\n\t\t<view class=\"u-content\">\n\t\t\t<u-button type=\"primary\" @click=\"logout\" text=\"退出登录\" :customStyle=\"{marginTop: '15px'}\"></u-button>\n\t\t</view>\n\t\t<tab-bar :selectedIndex=\"3\"></tab-bar>\n\t</view>\n\t\n</template>\n\n<script>\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tuser() {\n\t\t\t\treturn this.$store.getters['user'];\n\t\t\t},\n\t\t\tvipTime() {\n\t\t\t\treturn uni.$u.timeFormat(new Date(this.\tuser.vipTime), 'yyyy-mm-dd')\n\t\t\t},\n\t\t\tdefaultGroup() {\n\t\t\t\treturn this.$store.getters['defaultGroup'];\n\t\t\t},\n\t\t\tdefaultBook() {\n\t\t\t\treturn this.$store.getters['defaultBook'];\n\t\t\t},\n\t\t\tapiUrl() {\n\t\t\t\treturn this.$store.getters['apiUrl'];\n\t\t\t}\n\t\t},\n\t\tonLoad: function() {\n\t\t\tif (!this.$store.getters.user) this.$store.dispatch('getSession');\n\t\t},\n\t\tmethods: {\n\t\t\tlogout() {\n\t\t\t\tuni.removeStorageSync('userToken');\n\t\t\t\tuni.reLaunch({ url: '/pages/login/login' });\n\t\t\t},\n\t\t\topenBooks() {\n\t\t\t\tuni.navigateTo({ url: '/pages/books/books' });\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style>\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/select/select.vue",
    "content": "<template>\n\t<view class=\"u-content\">\n\t\t<u-search v-model=\"keyword\" @change=\"keywordChange\" :showAction=\"false\" :customStyle=\"{margin: '15px 0'}\"></u-search>\n\t\t<u-checkbox-group v-if=\"multiple\" v-model=\"checkboxValue\" placement=\"row\" size=\"22\">\n\t\t\t<u-checkbox\n\t\t\t\t:customStyle=\"{marginBottom: '10px'}\"\n\t\t\t\tv-for=\"item in list\"\n\t\t\t\t:key=\"item.id\"\n\t\t\t\t:label=\"item.name\"\n\t\t\t\t:name=\"item.id\"\n\t\t\t>\n\t\t\t</u-checkbox>\n\t\t</u-checkbox-group>\n\t\t<u-radio-group v-else placement=\"row\" size=\"22\" @change=\"radioGroupChange\" v-model=\"radioValue\">\n\t\t\t<u-radio\n\t\t\t\t:customStyle=\"{marginBottom: '10px'}\"\n\t\t\t\tv-for=\"item in list\"\n\t\t\t\t:key=\"item.id\"\n\t\t\t\t:label=\"item.name\"\n\t\t\t\t:name=\"item.id\"\n\t\t\t>\n\t\t\t</u-radio>\n\t\t</u-radio-group>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tradioValue: undefined,\n\t\t\t\tmultiple: false,\n\t\t\t\tkeyword: '',\n\t\t\t\tcheckboxValue: []\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tlist() {\n\t\t\t\treturn this.$store.getters.selectList.filter(e => e.name.indexOf(this.keyword) >= 0)\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tradioGroupChange(n) {\n\t\t\t\tconst eventChannel = this.getOpenerEventChannel();\n\t\t\t\teventChannel.emit('selectedValue', n);\n\t\t\t\tuni.navigateBack();\n\t\t\t},\n\t\t},\n\t\tonLoad(option) {\n\t\t\tuni.setNavigationBarTitle({ title: option.title });\n\t\t\tthis.multiple = option.multiple;\n\t\t\tif (this.multiple) {\n\t\t\t\tthis.checkboxValue = option.value.split(',').map(e => parseInt(e));\n\t\t\t} else {\n\t\t\t\tthis.radioValue = parseInt(option.value);\n\t\t\t}\n\t\t\t\n\t\t},\n\t\tonUnload() {\n\t\t\tif (this.multiple) {\n\t\t\t\tconst eventChannel = this.getOpenerEventChannel();\n\t\t\t\teventChannel.emit('selectedValue', this.checkboxValue);\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style>\n.u-radio-group--row, .u-checkbox-group--row {\n\tflex-wrap: wrap !important;\n\tgap: 10px !important;\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages/test/test.vue",
    "content": "<template>\n\t<view>\n\t\t<u-navbar\n\t\t\t:safeAreaInsetTop=\"true\"\n\t\t\t:placeholder=\"true\"\n\t\t\t:border=\"true\"\n\t\t\t:fixed=\"true\"\n\t\t\tleftIcon=\"reload\"\n\t\t\tleftIconSize=\"25px\"\n\t\t>\n\t\t\t<view class=\"u-nav-slot\" slot=\"right\">\n\t\t\t\t<u-icon name=\"list\" size=\"25\"></u-icon>\n\t\t\t\t<u-icon name=\"plus\" size=\"25\"></u-icon>\n\t\t\t</view>\n\t\t</u-navbar>\n\t\t\n\t\t<uni-list>\n\t\t\t<uni-list-item  title=\"列表文字\" ></uni-list-item>\n\t\t\t<uni-list-item :disabled=\"true\" title=\"列表禁用状态\" ></uni-list-item>\n\t\t</uni-list>\n\t\t\n\t\t\n\t\t<u-tabbar\n\t\t\t:value=\"selectedIndex\"\n\t\t\t:fixed=\"true\"\n\t\t\t:placeholder=\"true\">\n\t\t\t<u-tabbar-item text=\"账户\" icon=\"home\"></u-tabbar-item>\n\t\t\t<u-tabbar-item text=\"流水\" icon=\"list-dot\"></u-tabbar-item>\n\t\t\t<u-button type=\"primary\" text=\"记账\" shape=\"circle\" @click=\"addFlow\"></u-button>\n\t\t\t<u-tabbar-item text=\"图表\" icon=\"eye\"></u-tabbar-item>\n\t\t\t<u-tabbar-item text=\"我的\" icon=\"account\"></u-tabbar-item>\n\t\t</u-tabbar>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tselectedIndex: 0,\n\t\t\t\tindexList: [],\n\t\t\t\turls: [\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/1.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/2.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/3.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/4.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/5.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/6.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/7.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/8.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/9.jpg',\n\t\t\t\t\t'https://cdn.uviewui.com/uview/album/10.jpg',\n\t\t\t\t]\n\t\t\t}\n\t\t},\n\t\tonLoad() {\n\t\t\tthis.loadmore()\n\t\t},\n\t\tmethods: {\n\t\t\tscrolltolower() {\n\t\t\t\tthis.loadmore()\n\t\t\t},\n\t\t\tloadmore() {\n\t\t\t\tfor (let i = 0; i < 30; i++) {\n\t\t\t\t\tthis.indexList.push({\n\t\t\t\t\t\turl: this.urls[uni.$u.random(0, this.urls.length - 1)]\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\">\n.u-nav-slot {\n\t@include flex;\n\tmargin-right: 90px;\n\talign-items: center;\n\tjustify-content: space-between;\n\tborder-width: 0.5px;\n\tborder-radius: 100px;\n\tborder-color: $u-border-color;\n\tpadding: 3px 7px;\n\tgap: 10px;\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/pages.json",
    "content": "{\n\t\"easycom\": {\n\t\t\"autoscan\": true, //是否开启自动扫描，开启后将会自动扫描符合components/组件名称/组件名称.vue目录结构的组件\n\t\t\"custom\": {\t\t\t\n\t\t\t\"^u-(.*)\": \"@/uni_modules/uview-ui/components/u-$1/u-$1.vue\"\n\t\t}\n\t},\n\t\"pages\": [ //pages数组中第一项表示应用启动页，参考：https://uniapp.dcloud.io/collocation/pages\n\t\t{\n\t\t\t\"path\": \"pages/index/index\",\n\t\t\t\"style\": {\n\t\t\t\t\"navigationBarTitleText\": \"\",\n\t\t\t\t\"navigationStyle\": \"custom\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t    \"path\": \"pages/login/login\",\n\t\t    \"style\": {\n\t\t        \"navigationBarTitleText\": \"登录\",\n\t\t\t\t\"titleAlign\": \"center\",\n\t\t        \"enablePullDownRefresh\": false\n\t\t    }\n\t\t},\n        {\n            \"path\": \"pages/flow-form/flow-form\",\n            \"style\": {\n                \"navigationBarTitleText\": \"\",\n                \"enablePullDownRefresh\": false\n            }\n        },\n\t\t{\n            \"path\": \"pages/select/select\",\n            \"style\": {\n                \"navigationBarTitleText\": \"\",\n                \"enablePullDownRefresh\": false\n            }\n        },\n        {\n            \"path\": \"pages/flow-detail/flow-detail\",\n            \"style\": {\n                \"navigationBarTitleText\": \"账单详情\",\n                \"enablePullDownRefresh\": false\n            }\n            \n        },\n        {\n            \"path\" : \"pages/account-detail/account-detail\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"账户详情\",\n                \"enablePullDownRefresh\": false\n            }\n            \n        }\n        ,{\n            \"path\" : \"pages/accounts/accounts\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"\",\n               \"enablePullDownRefresh\": true,\n               \"navigationStyle\": \"custom\"\n            }\n            \n        }\n        ,{\n            \"path\" : \"pages/flows/flows\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"\",\n                \"enablePullDownRefresh\": true,\n\t\t\t\t\"navigationStyle\": \"custom\"\n            }\n        }\n        ,{\n            \"path\" : \"pages/charts/charts\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"\",\n                \"enablePullDownRefresh\": false,\n\t\t\t\t\"navigationStyle\": \"custom\"\n            }\n            \n        }\n        ,{\n            \"path\" : \"pages/my/my\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"我的\",\n                \"enablePullDownRefresh\": false,\n\t\t\t\t\"navigationStyle\": \"custom\"\n            }\n            \n        }\n        ,{\n            \"path\" : \"pages/books/books\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"\",\n                \"enablePullDownRefresh\": false\n            }\n            \n        }\n    ],\n\t\"globalStyle\": {\n\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\"navigationBarTitleText\": \"uni-app\",\n\t\t\"navigationBarBackgroundColor\": \"#F8F8F8\",\n\t\t\"backgroundColor\": \"#F8F8F8\"\n\t},\n\t\"uniIdRouter\": {}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/store/index.js",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\n\nimport session from './modules/session'\nimport account from './modules/account'\nimport payee from './modules/payee'\nimport tag from './modules/tag'\nimport select from './modules/select'\nimport expenseCategory from './modules/expenseCategory'\nimport incomeCategory from './modules/incomeCategory'\nimport modelForm from './modules/modelForm'\n\nVue.use(Vuex) // vue的插件机制\n\nexport default new Vuex.Store({\n\tmodules: {\n\t\tsession, account, select, expenseCategory, incomeCategory, payee, tag, modelForm\n\t}\n})"
  },
  {
    "path": "bookkeeping_user_uniapp/store/modules/account.js",
    "content": "import { getExpenseableAccounts, getIncomeableAccounts, getTransferFromAbleAccounts, getTransferToAbleAccounts, getEnableAccounts } from '@/config/api.js';\n\nexport default {\n\t\n\tstate: () => {\n\t\treturn {\n\t\t\texpenseableAccounts: undefined,\n\t\t\tincomeableAccounts: undefined,\n\t\t\ttransferFromAbleAccounts: undefined,\n\t\t\ttransferToAbleAccounts: undefined,\n\t\t\tenableAccounts: undefined,\n\t\t}\n\t},\n\t\n\tmutations: {\n\t\tsetExpenseableAccounts(state, data) {\n\t\t\tstate.expenseableAccounts = data;\n\t\t},\n\t\tsetIncomeableAccounts(state, data) {\n\t\t\tstate.incomeableAccounts = data;\n\t\t},\n\t\tsetTransferFromAbleAccounts(state, data) {\n\t\t\tstate.transferFromAbleAccounts = data;\n\t\t},\n\t\tsetTransferToAbleAccounts(state, data) {\n\t\t\tstate.transferToAbleAccounts = data;\n\t\t},\n\t\tsetEnableAccounts(state, data) {\n\t\t\tstate.enableAccounts = data;\n\t\t}\n\t},\n\t\n\tactions: {\n\t\tasync getExpenseableAccounts({commit}) {\n\t\t\tcommit('setExpenseableAccounts', await getExpenseableAccounts());\n\t\t},\n\t\tasync getIncomeableAccounts({commit}) {\n\t\t\tcommit('setIncomeableAccounts', await getIncomeableAccounts());\n\t\t},\n\t\tasync getTransferFromAbleAccounts({commit}) {\n\t\t\tcommit('setTransferFromAbleAccounts', await getTransferFromAbleAccounts());\n\t\t},\n\t\tasync getTransferToAbleAccounts({commit}) {\n\t\t\tcommit('setTransferToAbleAccounts', await getTransferToAbleAccounts());\n\t\t},\n\t\tasync getEnableAccounts({commit}) {\n\t\t\tcommit('setEnableAccounts', await getEnableAccounts());\n\t\t}\n\t},\n\t\n\tgetters: {\n\t\texpenseableAccounts (state) {\n\t\t\treturn state.expenseableAccounts;\n\t\t},\n\t\tincomeableAccounts (state) {\n\t\t\treturn state.incomeableAccounts;\n\t\t},\n\t\ttransferFromAbleAccounts (state) {\n\t\t\treturn state.transferFromAbleAccounts;\n\t\t},\n\t\ttransferToAbleAccounts (state) {\n\t\t\treturn state.transferToAbleAccounts;\n\t\t},\n\t\tenableAccounts (state) {\n\t\t\treturn state.enableAccounts;\n\t\t}\n\t},\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/store/modules/expenseCategory.js",
    "content": "import { getExpenseCategories } from '@/config/api.js';\n\nexport default {\n\t\n\tstate: () => {\n\t\treturn {\n\t\t\texpenseCategories: undefined,\n\t\t}\n\t},\n\t\n\tmutations: {\n\t\tsetExpenseCategories(state, data) {\n\t\t\tstate.expenseCategories = data;\n\t\t}\n\t},\n\t\n\tactions: {\n\t\tasync getExpenseCategories({commit}) {\n\t\t\tcommit('setExpenseCategories', await getExpenseCategories());\n\t\t}\n\t},\n\t\n\tgetters: {\n\t\texpenseCategories (state) {\n\t\t\treturn state.expenseCategories;\n\t\t}\n\t},\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/store/modules/incomeCategory.js",
    "content": "import { getIncomeCategories } from '@/config/api.js';\n\nexport default {\n\t\n\tstate: () => {\n\t\treturn {\n\t\t\tincomeCategories: undefined,\n\t\t}\n\t},\n\t\n\tmutations: {\n\t\tsetIncomeCategories(state, data) {\n\t\t\tstate.incomeCategories = data;\n\t\t}\n\t},\n\t\n\tactions: {\n\t\tasync getIncomeCategories({commit}) {\n\t\t\tcommit('setIncomeCategories', await getIncomeCategories());\n\t\t}\n\t},\n\t\n\tgetters: {\n\t\tincomeCategories (state) {\n\t\t\treturn state.incomeCategories;\n\t\t}\n\t},\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/store/modules/modelForm.js",
    "content": "export default {\n\tnamespaced: true,\n\tstate: () => {\n\t\treturn {\n\t\t\ttype: 1, //1-新增，2-修改，3-复制，4-退款\n\t\t\tmodel: undefined,\n\t\t}\n\t},\n\t\n\tmutations: {\n\t\tsetModel(state, model) {\n\t\t\tstate.model = model;\n\t\t},\n\t\tsetType(state, type) {\n\t\t\tstate.type = type;\n\t\t}\n\t},\n\t\n\tgetters: {\n\t\tmodel (state) {\n\t\t\treturn state.model;\n\t\t},\n\t\ttype (state) {\n\t\t\treturn state.type\n\t\t}\n\t},\n\t\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/store/modules/payee.js",
    "content": "import { getExpenseablePayees, getIncomeablePayees, getEnablePayees } from '@/config/api.js';\n\nexport default {\n\t\n\tstate: () => {\n\t\treturn {\n\t\t\texpenseablePayees: undefined,\n\t\t\tincomeablePayees: undefined,\n\t\t\tenablePayees: undefined,\n\t\t}\n\t},\n\t\n\tmutations: {\n\t\tsetExpenseablePayees(state, data) {\n\t\t\tstate.expenseablePayees = data;\n\t\t},\n\t\tsetIncomeablePayees(state, data) {\n\t\t\tstate.incomeablePayees = data;\n\t\t},\n\t\tsetEnablePayees(state, data) {\n\t\t\tstate.enablePayees = data;\n\t\t}\n\t},\n\t\n\tactions: {\n\t\tasync getExpenseablePayees({commit}) {\n\t\t\tcommit('setExpenseablePayees', await getExpenseablePayees());\n\t\t},\n\t\tasync getIncomeablePayees({commit}) {\n\t\t\tcommit('setIncomeablePayees', await getIncomeablePayees());\n\t\t},\n\t\tasync getEnablePayees({commit}) {\n\t\t\tcommit('setEnablePayees', await getEnablePayees());\n\t\t}\n\t},\n\t\n\tgetters: {\n\t\texpenseablePayees (state) {\n\t\t\treturn state.expenseablePayees;\n\t\t},\n\t\tincomeablePayees (state) {\n\t\t\treturn state.incomeablePayees;\n\t\t},\n\t\tenablePayees (state) {\n\t\t\treturn state.enablePayees;\n\t\t}\n\t},\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/store/modules/select.js",
    "content": "export default {\n\t\n\tstate: () => {\n\t\treturn {\n\t\t\tselectList: undefined,\n\t\t}\n\t},\n\t\n\tmutations: {\n\t\tsetSelectList(state, list) {\n\t\t\tstate.selectList = list;\n\t\t}\n\t},\n\t\n\tactions: {\n\t\t\n\t},\n\t\n\tgetters: {\n\t\tselectList (state) {\n\t\t\treturn state.selectList;\n\t\t}\n\t},\n\t\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/store/modules/session.js",
    "content": "import { getSession } from '@/config/api.js';\n\nexport default {\n\t\n\tstate: () => {\n\t\treturn {\n\t\t\tuserToken: undefined,\n\t\t\tuser: undefined,\n\t\t\tdefaultBook: undefined,\n\t\t\tdefaultGroup: undefined,\n\t\t\tapiUrl: undefined,\n\t\t}\n\t},\n\t\n\tgetters: {\n\t\tuserToken(state) {\n\t\t\tif (state.userToken) {\n\t\t\t\treturn state.userToken;\n\t\t\t} else {\n\t\t\t\tconst userToken = uni.getStorageSync('userToken');\n\t\t\t\tstate.userToken = userToken;\n\t\t\t\treturn userToken;\n\t\t\t}\n\t\t},\n\t\tuser(state) {\n\t\t\treturn state.user;\n\t\t},\n\t\tdefaultBook(state) {\n\t\t\treturn state.defaultBook;\n\t\t},\n\t\tdefaultGroup(state) {\n\t\t\treturn state.defaultGroup;\n\t\t},\n\t\tdefaultCurrencyCode(state) {\n\t\t\treturn state.defaultGroup.defaultCurrencyCode\n\t\t},\n\t\tapiUrl(state) {\n\t\t\tif (state.apiUrl) {\n\t\t\t\treturn state.apiUrl;\n\t\t\t} else {\n\t\t\t\tconst apiUrl = uni.getStorageSync('apiUrl');\n\t\t\t\tstate.apiUrl = apiUrl;\n\t\t\t\treturn apiUrl;\n\t\t\t}\n\t\t},\n\t},\n\t\n\tactions: {\n\t\tasync getSession({ commit }) {\n\t\t\tconst data = await getSession();\n\t\t\tcommit('setUser', data.userSessionVO);\n\t\t\tcommit('setDefaultBook', data.defaultBook);\n\t\t\tcommit('setDefaultGroup', data.defaultGroup);\n\t\t}\n\t},\n\t\n\tmutations: {\n\t\tupdateToken: (state, userToken) => {\n\t\t\tstate.userToken = userToken;\n\t\t\tuni.setStorageSync('userToken', userToken);\n\t\t},\n\t\tsetUser: (state, user) => {\n\t\t\tstate.user = user;\n\t\t},\n\t\tsetDefaultBook: (state, book) => {\n\t\t\tstate.defaultBook = book;\n\t\t},\n\t\tsetDefaultGroup: (state, group) => {\n\t\t\tstate.defaultGroup = group;\n\t\t},\n\t\tupdateApiUrl: (state, apiUrl) => {\n\t\t\tstate.apiUrl = apiUrl;\n\t\t\tuni.setStorageSync('apiUrl', apiUrl);\n\t\t},\n\t}\n\t\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/store/modules/tag.js",
    "content": "import { getExpenseableTags, getIncomeableTags, getTransferableTags, getEnableTags } from '@/config/api.js';\n\nexport default {\n\t\n\tstate: () => {\n\t\treturn {\n\t\t\texpenseableTags: undefined,\n\t\t\tincomeableTags: undefined,\n\t\t\ttransferableTags: undefined,\n\t\t\tenableTags: undefined,\n\t\t}\n\t},\n\t\n\tmutations: {\n\t\tsetExpenseableTags(state, data) {\n\t\t\tstate.expenseableTags = data;\n\t\t},\n\t\tsetIncomeableTags(state, data) {\n\t\t\tstate.incomeableTags = data;\n\t\t},\n\t\tsetTransferableTags(state, data) {\n\t\t\tstate.transferableTags = data;\n\t\t},\n\t\tsetEnableTags(state, data) {\n\t\t\tstate.enableTags = data;\n\t\t},\n\t},\n\t\n\tactions: {\n\t\tasync getExpenseableTags({commit}) {\n\t\t\tcommit('setExpenseableTags', await getExpenseableTags());\n\t\t},\n\t\tasync getIncomeableTags({commit}) {\n\t\t\tcommit('setIncomeableTags', await getIncomeableTags());\n\t\t},\n\t\tasync getTransferableTags({commit}) {\n\t\t\tcommit('setTransferableTags', await getTransferableTags());\n\t\t},\n\t\tasync getEnableTags({commit}) {\n\t\t\tcommit('setEnableTags', await getEnableTags());\n\t\t}\n\t},\n\t\n\tgetters: {\n\t\texpenseableTags (state) {\n\t\t\treturn state.expenseableTags;\n\t\t},\n\t\tincomeableTags (state) {\n\t\t\treturn state.incomeableTags;\n\t\t},\n\t\ttransferableTags (state) {\n\t\t\treturn state.transferableTags;\n\t\t},\n\t\tenableTags (state) {\n\t\t\treturn state.enableTags;\n\t\t}\n\t},\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/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@import '@/uni_modules/uview-ui/theme.scss';\n/* 颜色变量 */\n\n/* 行为相关颜色 */\n$uni-color-primary: #007aff;\n$uni-color-success: #4cd964;\n$uni-color-warning: #f0ad4e;\n$uni-color-error: #dd524d;\n\n/* 文字基本颜色 */\n$uni-text-color:#333;//基本色\n$uni-text-color-inverse:#fff;//反色\n$uni-text-color-grey:#999;//辅助灰色，如加载更多的提示信息\n$uni-text-color-placeholder: #808080;\n$uni-text-color-disable:#c0c0c0;\n\n/* 背景颜色 */\n$uni-bg-color:#ffffff;\n$uni-bg-color-grey:#f8f8f8;\n$uni-bg-color-hover:#f1f1f1;//点击状态颜色\n$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色\n\n/* 边框颜色 */\n$uni-border-color:#c8c7cc;\n\n/* 尺寸变量 */\n\n/* 文字尺寸 */\n$uni-font-size-sm:12px;\n$uni-font-size-base:14px;\n$uni-font-size-lg:16;\n\n/* 图片尺寸 */\n$uni-img-size-sm:20px;\n$uni-img-size-base:26px;\n$uni-img-size-lg:40px;\n\n/* Border Radius */\n$uni-border-radius-sm: 2px;\n$uni-border-radius-base: 3px;\n$uni-border-radius-lg: 6px;\n$uni-border-radius-circle: 50%;\n\n/* 水平间距 */\n$uni-spacing-row-sm: 5px;\n$uni-spacing-row-base: 10px;\n$uni-spacing-row-lg: 15px;\n\n/* 垂直间距 */\n$uni-spacing-col-sm: 4px;\n$uni-spacing-col-base: 8px;\n$uni-spacing-col-lg: 12px;\n\n/* 透明度 */\n$uni-opacity-disabled: 0.3; // 组件禁用态的透明度\n\n/* 文章场景相关 */\n$uni-color-title: #2C405A; // 文章标题颜色\n$uni-font-size-title:20px;\n$uni-color-subtitle: #555555; // 二级标题颜色\n$uni-font-size-subtitle:26px;\n$uni-color-paragraph: #3F536E; // 文章段落颜色\n$uni-font-size-paragraph:15px;\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/changelog.md",
    "content": "## 2.4.3-20220505（2022-05-05）\n- 秋云图表组件 修复开启canvas2d后将series赋值为空数组显示加载图标时，再次赋值后画布闪动的bug\n- 秋云图表组件 修复升级hbx最新版后ECharts的highlight方法报错的bug\n- uCharts.js 雷达图新增参数opts.extra.radar.gridEval，数据点位网格抽希，默认1\n- uCharts.js 雷达图新增参数opts.extra.radar.axisLabel，\t是否显示刻度点值，默认false\n- uCharts.js 雷达图新增参数opts.extra.radar.axisLabelTofix，刻度点值小数位数，默认0\n- uCharts.js 雷达图新增参数opts.extra.radar.labelPointShow，是否显示末端刻度圆点，默认false\n- uCharts.js 雷达图新增参数opts.extra.radar.labelPointRadius，刻度圆点的半径，默认3\n- uCharts.js 雷达图新增参数opts.extra.radar.labelPointColor，刻度圆点的颜色，默认#cccccc\n- uCharts.js 雷达图新增参数opts.extra.radar.linearType，渐变色类型，可选值\"none\"关闭渐变,\"custom\"开启渐变\n- uCharts.js 雷达图新增参数opts.extra.radar.customColor，自定义渐变颜色，数组类型对应series的数组长度以匹配不同series颜色的不同配色方案，例如[\"#FA7D8D\", \"#EB88E2\"]\n- uCharts.js 雷达图优化支持series.textColor、series.textSize属性\n- uCharts.js 柱状图中温度计式图标，优化支持全圆角类型，修复边框有缝隙的bug，详见官网【演示】中的温度计图表\n- uCharts.js 柱状图新增参数opts.extra.column.activeWidth，当前点击柱状图的背景宽度，默认一个单元格单位\n- uCharts.js 混合图增加opts.extra.mix.area.gradient 区域图是否开启渐变色\n- uCharts.js 混合图增加opts.extra.mix.area.opacity 区域图透明度，默认0.2\n- uCharts.js 饼图、圆环图、玫瑰图、漏斗图，增加opts.series[0].data[i].labelText，自定义标签文字，避免formatter格式化的繁琐，详见官网【演示】中的饼图\n- uCharts.js 饼图、圆环图、玫瑰图、漏斗图，增加opts.series[0].data[i].labelShow，自定义是否显示某一个指示标签，避免因饼图类别太多导致标签重复或者居多导致图形变形的问题，详见官网【演示】中的饼图\n- uCharts.js 增加opts.series[i].legendText/opts.series[0].data[i].legendText（与series.name同级）自定义图例显示文字的方法\n- uCharts.js 优化X轴、Y轴formatter格式化方法增加形参，统一为fromatter:function(value,index,opts){}\n- uCharts.js 修复横屏模式下无法使用双指缩放方法的bug\n- uCharts.js 修复当只有一条数据或者多条数据值相等的时候Y轴自动计算的最大值错误的bug\n- 【官网模板】增加外部自定义图例与图表交互的例子，[点击跳转](https://www.ucharts.cn/v2/#/layout/info?id=2)\n\n## 注意：非unimodules 版本如因更新 hbx 至 3.4.7 导致报错如下，请到码云更新非 unimodules 版本组件，[点击跳转](https://gitee.com/uCharts/uCharts/tree/master/uni-app/uCharts-%E7%BB%84%E4%BB%B6)\n> Error in callback for immediate watcher \"uchartsOpts\": \"SyntaxError: Unexpected token u in JSON at position 0\"\n## 2.4.2-20220421（2022-04-21）\n- 秋云图表组件 修复HBX升级3.4.6.20220420版本后echarts报错的问题\n## 2.4.2-20220420（2022-04-20）\n## 重要！此版本uCharts新增了很多功能，修复了诸多已知问题\n- 秋云图表组件 新增onzoom开启双指缩放功能（仅uCharts），前提需要直角坐标系类图表类型，并且ontouch为true、opts.enableScroll为true，详见实例项目K线图\n- 秋云图表组件 新增optsWatch是否监听opts变化，关闭optsWatch后，动态修改opts不会触发图表重绘\n- 秋云图表组件 修复开启canvas2d功能后，动态更新数据后画布闪动的bug\n- 秋云图表组件 去除directory属性，改为自动获取echarts.min.js路径（升级不受影响）\n- 秋云图表组件 增加getImage()方法及@getImage事件，通过ref调用getImage()方法获，触发@getImage事件获取当前画布的base64图片文件流。\n- 秋云图表组件 支付宝、字节跳动、飞书、快手小程序支持开启canvas2d同层渲染设置。\n- 秋云图表组件 新增加【非uniCloud】版本组件，避免有些不需要uniCloud的使用组件发布至小程序需要提交隐私声明问题，请到码云[【非uniCloud版本】](https://gitee.com/uCharts/uCharts/tree/master/uni-app/uCharts-%E7%BB%84%E4%BB%B6)，或npm[【非uniCloud版本】](https://www.npmjs.com/package/@qiun/uni-ucharts)下载使用。\n- uCharts.js 新增dobuleZoom双指缩放功能\n- uCharts.js 新增山峰图type=\"mount\"，数据格式为饼图类格式，不需要传入categories，具体详见新版官网在线演示\n- uCharts.js 修复折线图当数据中存在null时tooltip报错的bug\n- uCharts.js 修复饼图类当画布比较小时自动计算的半径是负数报错的bug\n- uCharts.js 统一各图表类型的series.formatter格式化方法的形参为(val, index, series, opts)，方便格式化时有更多参数可用\n- uCharts.js 标记线功能增加labelText自定义显示文字，增加labelAlign标签显示位置（左侧或右侧），增加标签显示位置微调labelOffsetX、labelOffsetY\n- uCharts.js 修复条状图当数值很小时开启圆角后样式错误的bug\n- uCharts.js 修复X轴开启disabled后，X轴仍占用空间的bug\n- uCharts.js 修复X轴开启滚动条并且开启rotateLabel后，X轴文字与滚动条重叠的bug\n- uCharts.js 增加X轴rotateAngle文字旋转自定义角度，取值范围(-90至90)\n- uCharts.js 修复地图文字标签层级显示不正确的bug\n- uCharts.js 修复饼图、圆环图、玫瑰图当数据全部为0的时候不显示数据标签的bug\n- uCharts.js 修复当opts.padding上边距为0时，Y轴顶部刻度标签位置不正确的bug\n\n## 另外我们还开发了各大原生小程序组件，已发布至码云和npm\n[https://gitee.com/uCharts/uCharts](https://gitee.com/uCharts/uCharts)\n[https://www.npmjs.com/~qiun](https://www.npmjs.com/~qiun)\n\n## 对于原生uCharts文档我们已上线新版官方网站，详情点击下面链接进入官网\n[https://www.uCharts.cn/v2/](https://www.ucharts.cn/v2/)\n## 2.3.7-20220122（2022-01-22）\n## 重要！使用vue3编译，请使用cli模式并升级至最新依赖，HbuilderX编译需要使用3.3.8以上版本\n- uCharts.js 修复uni-app平台组件模式使用vue3编译到小程序报错的bug。\n## 2.3.7-20220118（2022-01-18）\n## 注意，使用vue3的前提是需要3.3.8.20220114-alpha版本的HBuilder！\n## 2.3.67-20220118（2022-01-18）\n- 秋云图表组件 组件初步支持vue3，全端编译会有些问题，具体详见下面修改：\n1. 小程序端运行时，在uni_modules文件夹的qiun-data-charts.js中搜索 new uni_modules_qiunDataCharts_js_sdk_uCharts_uCharts.uCharts，将.uCharts去掉。\n2. 小程序端发行时，在uni_modules文件夹的qiun-data-charts.js中搜索 new e.uCharts，将.uCharts去掉，变为 new e。\n3. 如果觉得上述步骤比较麻烦，如果您的项目只编译到小程序端，可以修改u-charts.js最后一行导出方式，将 export default uCharts;变更为 export default { uCharts: uCharts }; 这样变更后，H5和App端的renderjs会有问题，请开发者自行选择。（此问题非组件问题，请等待DC官方修复Vue3的小程序端）\n## 2.3.6-20220111（2022-01-11）\n- 秋云图表组件 修改组件 props 属性中的 background 默认值为 rgba(0,0,0,0)\n## 2.3.6-20211201（2021-12-01）\n- uCharts.js 修复bar条状图开启圆角模式时，值很小时圆角渲染错误的bug\n## 2.3.5-20211014（2021-10-15）\n- uCharts.js 增加vue3的编译支持（仅原生uCharts，qiun-data-charts组件后续会支持，请关注更新）\n## 2.3.4-20211012（2021-10-12）\n- 秋云图表组件 修复 mac os x 系统 mouseover 事件丢失的 bug\n## 2.3.3-20210706（2021-07-06）\n- uCharts.js 增加雷达图开启数据点值（opts.dataLabel）的显示\n## 2.3.2-20210627（2021-06-27）\n- 秋云图表组件 修复tooltipCustom个别情况下传值不正确报错TypeError: Cannot read property 'name' of undefined的bug\n## 2.3.1-20210616（2021-06-16）\n- uCharts.js 修复圆角柱状图使用4角圆角时，当数值过大时不正确的bug\n## 2.3.0-20210612（2021-06-12）\n- uCharts.js 【重要】uCharts增加nvue兼容，可在nvue项目中使用gcanvas组件渲染uCharts，[详见码云uCharts-demo-nvue](https://gitee.com/uCharts/uCharts)\n- 秋云图表组件 增加tapLegend属性，是否开启图例点击交互事件\n- 秋云图表组件 getIndex事件中增加返回uCharts实例中的opts参数，以便在页面中调用参数\n- 示例项目 pages/other/other.vue增加app端自定义tooltip的方法，详见showOptsTooltip方法\n## 2.2.1-20210603（2021-06-03）\n- uCharts.js 修复饼图、圆环图、玫瑰图，当起始角度不为0时，tooltip位置不准确的bug\n- uCharts.js 增加温度计式柱状图开启顶部半圆形的配置\n## 2.2.0-20210529（2021-05-29）\n- uCharts.js 增加条状图type=\"bar\"\n- 示例项目 pages/ucharts/ucharts.vue增加条状图的demo\n## 2.1.7-20210524（2021-05-24）\n- uCharts.js 修复大数据量模式下曲线图不平滑的bug\n## 2.1.6-20210523（2021-05-23）\n- 秋云图表组件 修复小程序端开启滚动条更新数据后滚动条位置不符合预期的bug\n## 2.1.5-2021051702（2021-05-17）\n- uCharts.js 修复自定义Y轴min和max值为0时不能正确显示的bug\n## 2.1.5-20210517（2021-05-17）\n- uCharts.js 修复Y轴自定义min和max时，未按指定的最大值最小值显示坐标轴刻度的bug\n## 2.1.4-20210516（2021-05-16）\n- 秋云图表组件 优化onWindowResize防抖方法\n- 秋云图表组件 修复APP端uCharts更新数据时，清空series显示loading图标后再显示图表，图表抖动的bug\n- uCharts.js 修复开启canvas2d后，x轴、y轴、series自定义字体大小未按比例缩放的bug\n- 示例项目 修复format-e.vue拼写错误导致app端使用uCharts渲染图表\n## 2.1.3-20210513（2021-05-13）\n- 秋云图表组件 修改uCharts变更chartData数据为updateData方法，支持带滚动条的数据动态打点\n- 秋云图表组件 增加onWindowResize防抖方法 fix by ど誓言，如尘般染指流年づ \n- 秋云图表组件 H5或者APP变更chartData数据显示loading图表时，原数据闪现的bug\n- 秋云图表组件 props增加errorReload禁用错误点击重新加载的方法\n- uCharts.js 增加tooltip显示category（x轴对应点位）标题的功能，opts.extra.tooltip.showCategory，默认为false\n- uCharts.js 修复mix混合图只有柱状图时，tooltip的分割线显示位置不正确的bug\n- uCharts.js 修复开启滚动条，图表在拖动中动态打点，滚动条位置不正确的bug\n- uCharts.js 修复饼图类数据格式为echarts数据格式，series为空数组报错的bug\n- 示例项目 修改uCharts.js更新到v2.1.2版本后，@getIndex方法获取索引值变更为e.currentIndex.index\n- 示例项目 pages/updata/updata.vue增加滚动条拖动更新（数据动态打点）的demo\n- 示例项目 pages/other/other.vue增加errorReload禁用错误点击重新加载的demo\n## 2.1.2-20210509（2021-05-09）\n秋云图表组件 修复APP端初始化时就传入chartData或lacaldata不显示图表的bug\n## 2.1.1-20210509（2021-05-09）\n- 秋云图表组件 变更ECharts的eopts配置在renderjs内执行，支持在config-echarts.js配置文件内写function配置。\n- 秋云图表组件 修复APP端报错Prop being mutated: \"onmouse\"错误的bug。\n- 秋云图表组件 修复APP端报错Error: Not Found：Page[6][-1,27] at view.umd.min.js:1的bug。\n## 2.1.0-20210507（2021-05-07）\n- 秋云图表组件 修复初始化时就有数据或者数据更新的时候loading加载动画闪动的bug\n- uCharts.js 修复x轴format方法categories为字符串类型时返回NaN的bug\n- uCharts.js 修复series.textColor、legend.fontColor未执行全局默认颜色的bug\n## 2.1.0-20210506（2021-05-06）\n- 秋云图表组件 修复极个别情况下报错item.properties undefined的bug\n- 秋云图表组件 修复极个别情况下关闭加载动画reshow不起作用，无法显示图表的bug\n- 示例项目 pages/ucharts/ucharts.vue 增加时间轴折线图（type=\"tline\"）、时间轴区域图（type=\"tarea\"）、散点图（type=\"scatter\"）、气泡图demo（type=\"bubble\"）、倒三角形漏斗图（opts.extra.funnel.type=\"triangle\"）、金字塔形漏斗图（opts.extra.funnel.type=\"pyramid\"）\n- 示例项目 pages/format-u/format-u.vue 增加X轴format格式化示例\n- uCharts.js 升级至v2.1.0版本\n- uCharts.js 修复 玫瑰图面积模式点击tooltip位置不正确的bug\n- uCharts.js 修复 玫瑰图点击图例，只剩一个类别显示空白的bug\n- uCharts.js 修复 饼图类图点击图例，其他图表tooltip位置某些情况下不准的bug\n- uCharts.js 修复 x轴为矢量轴（时间轴）情况下，点击tooltip位置不正确的bug\n- uCharts.js 修复 词云图获取点击索引偶尔不准的bug\n- uCharts.js 增加 直角坐标系图表X轴format格式化方法（原生uCharts.js用法请使用formatter）\n- uCharts.js 增加 漏斗图扩展配置，倒三角形（opts.extra.funnel.type=\"triangle\"），金字塔形（opts.extra.funnel.type=\"pyramid\"）\n- uCharts.js 增加 散点图（opts.type=\"scatter\"）、气泡图（opts.type=\"bubble\"）\n- 后期计划 完善散点图、气泡图，增加markPoints标记点，增加横向条状图。\n## 2.0.0-20210502（2021-05-02）\n- uCharts.js 修复词云图获取点击索引不正确的bug\n## 2.0.0-20210501（2021-05-01）\n- 秋云图表组件 修复QQ小程序、百度小程序在关闭动画效果情况下，v-for循环使用图表，显示不正确的bug\n## 2.0.0-20210426（2021-04-26）\n- 秋云图表组件 修复QQ小程序不支持canvas2d的bug\n- 秋云图表组件 修复钉钉小程序某些情况点击坐标计算错误的bug\n- uCharts.js 增加 extra.column.categoryGap 参数，柱状图类每个category点位（X轴点）柱子组之间的间距\n- uCharts.js 增加 yAxis.data[i].titleOffsetY 参数，标题纵向偏移距离，负数为向上偏移，正数向下偏移\n- uCharts.js 增加 yAxis.data[i].titleOffsetX 参数，标题横向偏移距离，负数为向左偏移，正数向右偏移\n- uCharts.js 增加 extra.gauge.labelOffset 参数，仪表盘标签文字径向便宜距离，默认13px\n## 2.0.0-20210422-2（2021-04-22）\n秋云图表组件 修复 formatterAssign 未判断 args[key] == null 的情况导致栈溢出的 bug\n## 2.0.0-20210422（2021-04-22）\n- 秋云图表组件 修复H5、APP、支付宝小程序、微信小程序canvas2d模式下横屏模式的bug\n## 2.0.0-20210421（2021-04-21）\n- uCharts.js 修复多行图例的情况下，图例在上方或者下方时，图例float为左侧或者右侧时，第二行及以后的图例对齐方式不正确的bug\n## 2.0.0-20210420（2021-04-20）\n- 秋云图表组件 修复微信小程序开启canvas2d模式后，windows版微信小程序不支持canvas2d模式的bug\n- 秋云图表组件 修改非uni_modules版本为v2.0版本qiun-data-charts组件\n## 2.0.0-20210419（2021-04-19）\n## v1.0版本已停更，建议转uni_modules版本组件方式调用，点击右侧绿色【使用HBuilderX导入插件】即可使用，示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。\n## 初次使用如果提示未注册&lt;qiun-data-charts&gt;组件，请重启HBuilderX，如仍不好用，请重启电脑；\n## 如果是cli项目，请尝试清理node_modules，重新install，还不行就删除项目，再重新install。\n## 此问题已于DCloud官方确认，HBuilderX下个版本会修复。\n## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)\n## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍，右侧蓝色按钮示例项目请看2遍！ </font> \n## [DEMO演示及在线生成工具（v2.0文档）https://demo.ucharts.cn](https://demo.ucharts.cn)\n## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) \n- uCharts.js 修复混合图中柱状图单独设置颜色不生效的bug\n- uCharts.js 修复多Y轴单独设置fontSize时，开启canvas2d后，未对应放大字体的bug\n## 2.0.0-20210418（2021-04-18）\n- 秋云图表组件 增加directory配置，修复H5端history模式下如果发布到二级目录无法正确加载echarts.min.js的bug\n## 2.0.0-20210416（2021-04-16）\n## v1.0版本已停更，建议转uni_modules版本组件方式调用，点击右侧绿色【使用HBuilderX导入插件】即可使用，示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。\n## 初次使用如果提示未注册&lt;qiun-data-charts&gt;组件，请重启HBuilderX，如仍不好用，请重启电脑；\n## 如果是cli项目，请尝试清理node_modules，重新install，还不行就删除项目，再重新install。\n## 此问题已于DCloud官方确认，HBuilderX下个版本会修复。\n## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)\n## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍，右侧蓝色按钮示例项目请看2遍！ </font> \n## [DEMO演示及在线生成工具（v2.0文档）https://demo.ucharts.cn](https://demo.ucharts.cn)\n## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) \n- 秋云图表组件 修复APP端某些情况下报错`Not Found Page`的bug，fix by 高级bug开发技术员\n- 示例项目 修复APP端v-for循环某些情况下报错`Not Found Page`的bug，fix by 高级bug开发技术员\n- uCharts.js 修复非直角坐标系tooltip提示窗右侧超出未变换方向显示的bug\n## 2.0.0-20210415（2021-04-15）\n- 秋云图表组件 修复H5端发布到二级目录下echarts无法加载的bug\n- 秋云图表组件 修复某些情况下echarts.off('finished')移除监听事件报错的bug\n## 2.0.0-20210414（2021-04-14）\n## v1.0版本已停更，建议转uni_modules版本组件方式调用，点击右侧绿色【使用HBuilderX导入插件】即可使用，示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。\n## 初次使用如果提示未注册&lt;qiun-data-charts&gt;组件，请重启HBuilderX，如仍不好用，请重启电脑；\n## 如果是cli项目，请尝试清理node_modules，重新install，还不行就删除项目，再重新install。\n## 此问题已于DCloud官方确认，HBuilderX下个版本会修复。\n## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn)\n## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍，右侧蓝色按钮示例项目请看2遍！ </font> \n## [DEMO演示及在线生成工具（v2.0文档）https://demo.ucharts.cn](https://demo.ucharts.cn)\n## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) \n- 秋云图表组件 修复H5端在cli项目下ECharts引用地址错误的bug\n- 示例项目 增加ECharts的formatter用法的示例(详见示例项目format-e.vue)\n- uCharts.js 增加圆环图中心背景色的配置extra.ring.centerColor\n- uCharts.js 修复微信小程序安卓端柱状图开启透明色后显示不正确的bug\n## 2.0.0-20210413（2021-04-13）\n- 秋云图表组件 修复百度小程序多个图表真机未能正确获取根元素dom尺寸的bug\n- 秋云图表组件 修复百度小程序横屏模式方向不正确的bug\n- 秋云图表组件 修改ontouch时，@getTouchStart@getTouchMove@getTouchEnd的触发条件\n- uCharts.js 修复饼图类数据格式series属性不生效的bug\n- uCharts.js 增加时序区域图 详见示例项目中ucharts.vue\n## 2.0.0-20210412-2（2021-04-12）\n## v1.0版本已停更，建议转uni_modules版本组件方式调用，点击右侧绿色【使用HBuilderX导入插件】即可使用，示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。\n## 初次使用如果提示未注册&lt;qiun-data-charts&gt;组件，请重启HBuilderX。如仍不好用，请重启电脑，此问题已于DCloud官方确认，HBuilderX下个版本会修复。\n## [DEMO演示及在线生成工具（v2.0文档）https://demo.ucharts.cn](https://demo.ucharts.cn)\n## [图表组件在uniCloudAdmin中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) \n- 秋云图表组件 修复uCharts在APP端横屏模式下不能正确渲染的bug\n- 示例项目 增加ECharts柱状图渐变色、圆角柱状图、横向柱状图（条状图）的示例\n## 2.0.0-20210412（2021-04-12）\n- 秋云图表组件 修复created中判断echarts导致APP端无法识别，改回mounted中判断echarts初始化\n- uCharts.js 修复2d模式下series.textOffset未乘像素比的bug\n## 2.0.0-20210411（2021-04-11）\n## v1.0版本已停更，建议转uni_modules版本组件方式调用，点击右侧绿色【使用HBuilderX导入插件】即可使用，示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。\n## 初次使用如果提示未注册<qiun-data-charts>组件，请重启HBuilderX，并清空小程序开发者工具缓存。\n## [DEMO演示及在线生成工具（v2.0文档）https://demo.ucharts.cn](https://demo.ucharts.cn)\n## [图表组件在uniCloudAdmin中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) \n- uCharts.js 折线图区域图增加connectNulls断点续连的功能，详见示例项目中ucharts.vue\n- 秋云图表组件 变更初始化方法为created，变更type2d默认值为true，优化2d模式下组件初始化后dom获取不到的bug\n- 秋云图表组件 修复左右布局时，右侧图表点击坐标错误的bug，修复tooltip柱状图自定义颜色显示object的bug\n## 2.0.0-20210410（2021-04-10）\n- 修复左右布局时，右侧图表点击坐标错误的bug，修复柱状图自定义颜色tooltip显示object的bug\n- 增加标记线及柱状图自定义颜色的demo\n## 2.0.0-20210409（2021-04-08）\n## v1.0版本已停更，建议转uni_modules版本组件方式调用，点击右侧【使用HBuilderX导入插件】即可体验，DEMO演示及在线生成工具（v2.0文档）[https://demo.ucharts.cn](https://demo.ucharts.cn)\n## 图表组件在uniCloudAdmin中的应用 [UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) \n- uCharts.js 修复钉钉小程序百度小程序measureText不准确的bug，修复2d模式下饼图类activeRadius为按比例放大的bug\n- 修复组件在支付宝小程序端点击位置不准确的bug\n## 2.0.0-20210408（2021-04-07）\n- 修复组件在支付宝小程序端不能显示的bug（目前支付宝小程不能点击交互，后续修复）\n- uCharts.js 修复高分屏下柱状图类，圆弧进度条 自定义宽度不能按比例放大的bug\n## 2.0.0-20210407（2021-04-06）\n## v1.0版本已停更，建议转uni_modules版本组件方式调用，点击右侧【使用HBuilderX导入插件】即可体验，DEMO演示及在线生成工具（v2.0文档）[https://demo.ucharts.cn](https://demo.ucharts.cn)\n## 增加 通过tofix和unit快速格式化y轴的demo add by `howcode`\n## 增加 图表组件在uniCloudAdmin中的应用 [UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) \n## 2.0.0-20210406（2021-04-05）\n# 秋云图表组件+uCharts v2.0版本同步上线，使用方法详见https://demo.ucharts.cn帮助页\n## 2.0.0（2021-04-05）\n# 秋云图表组件+uCharts v2.0版本同步上线，使用方法详见https://demo.ucharts.cn帮助页\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue",
    "content": "<!-- \n * qiun-data-charts 秋云高性能跨全端图表组件\n * Copyright (c) 2021 QIUN® 秋云 https://www.ucharts.cn All rights reserved.\n * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n * 复制使用请保留本段注释，感谢支持开源！\n * 为方便更多开发者使用，如有更好的建议请提交码云 Pull Requests ！\n *\n * uCharts®官方网站\n * https://www.uCharts.cn\n * \n * 开源地址:\n * https://gitee.com/uCharts/uCharts\n * \n * uni-app插件市场地址：\n * http://ext.dcloud.net.cn/plugin?id=271\n * \n -->\n<template>\n  <view class=\"chartsview\" :id=\"'ChartBoxId'+cid\">\n    <view v-if=\"mixinDatacomLoading\">\n      <!-- 自定义加载状态，请改这里 -->\n      <qiun-loading :loadingType=\"loadingType\" />\n    </view>\n    <view v-if=\"mixinDatacomErrorMessage && errorShow\" @tap=\"reloading\">\n      <!-- 自定义错误提示，请改这里 -->\n      <qiun-error :errorMessage=\"errorMessage\" />\n    </view>\n    <!-- APP和H5采用renderjs渲染图表 -->\n    <!-- #ifdef APP-VUE || H5 -->\n    <block v-if=\"echarts\">\n      <view\n        :style=\"{ background: background }\"\n        style=\"width: 100%;height: 100%;\"\n        :data-directory=\"directory\"\n        :id=\"'EC'+cid\" \n        :prop=\"echartsOpts\" \n        :change:prop=\"rdcharts.ecinit\" \n        :resize=\"echartsResize\"\n        :change:resize=\"rdcharts.ecresize\"\n        v-show=\"showchart\"\n      />\n    </block>\n    <block v-else>\n      <view\n        v-on:tap=\"rdcharts.tap\"\n        v-on:mousemove=\"rdcharts.mouseMove\"\n        v-on:mousedown=\"rdcharts.mouseDown\"\n        v-on:mouseup=\"rdcharts.mouseUp\"\n        v-on:touchstart=\"rdcharts.touchStart\"\n        v-on:touchmove=\"rdcharts.touchMove\"\n        v-on:touchend=\"rdcharts.touchEnd\"\n        :id=\"'UC'+cid\"\n        :prop=\"uchartsOpts\"\n        :change:prop=\"rdcharts.ucinit\"\n      >\n        <canvas\n          :id=\"cid\"\n          :canvasId=\"cid\"\n          :style=\"{ width: cWidth + 'px', height: cHeight + 'px', background: background }\"\n          :disable-scroll=\"disableScroll\"\n          @error=\"_error\"\n          v-show=\"showchart\"\n        />\n      </view>\n    </block>\n    <!-- #endif -->\n    <!-- 支付宝小程序 -->\n    <!-- #ifdef MP-ALIPAY -->\n    <block v-if=\"ontouch\">\n      <canvas\n        :id=\"cid\"\n        :canvasId=\"cid\"\n        :width=\"cWidth * pixel\"\n        :height=\"cHeight * pixel\"\n        :style=\"{ width: cWidth + 'px', height: cHeight + 'px', background: background }\"\n        :disable-scroll=\"disScroll\"\n        @tap=\"_tap\"\n        @touchstart=\"_touchStart\"\n        @touchmove=\"_touchMove\"\n        @touchend=\"_touchEnd\"\n        @error=\"_error\"\n        v-show=\"showchart\"\n      />\n    </block>\n    <block v-if=\"!ontouch\">\n      <canvas\n        :id=\"cid\"\n        :canvasId=\"cid\"\n        :width=\"cWidth * pixel\"\n        :height=\"cHeight * pixel\"\n        :style=\"{ width: cWidth + 'px', height: cHeight + 'px', background: background }\"\n        :disable-scroll=\"disScroll\"\n        @tap=\"_tap\"\n        @error=\"_error\"\n        v-show=\"showchart\"\n      />\n    </block>\n    <!-- #endif -->\n    <!-- 其他小程序通过vue渲染图表 -->\n    <!-- #ifdef MP-360 || MP-BAIDU || MP-QQ || MP-TOUTIAO || MP-WEIXIN || MP-KUAISHOU || MP-LARK || MP-JD -->\n    <block v-if=\"type2d\">\n      <view v-if=\"ontouch\" @tap=\"_tap\">\n        <canvas\n          :id=\"cid\"\n          :canvasId=\"cid\"\n          :style=\"{ width: cWidth + 'px', height: cHeight + 'px', background: background }\"\n          type=\"2d\"\n          :disable-scroll=\"disScroll\"\n          @touchstart=\"_touchStart\"\n          @touchmove=\"_touchMove\"\n          @touchend=\"_touchEnd\"\n          @error=\"_error\"\n          v-show=\"showchart\"\n        />\n      </view>\n      <view v-if=\"!ontouch\" @tap=\"_tap\">\n        <canvas\n          :id=\"cid\"\n          :canvasId=\"cid\"\n          :style=\"{ width: cWidth + 'px', height: cHeight + 'px', background: background }\"\n          type=\"2d\"\n          :disable-scroll=\"disScroll\"\n          @error=\"_error\"\n          v-show=\"showchart\"\n        />\n      </view>\n    </block>\n    <block v-if=\"!type2d\">\n      <view v-if=\"ontouch\" @tap=\"_tap\">\n        <canvas\n          :id=\"cid\"\n          :canvasId=\"cid\"\n          :style=\"{ width: cWidth + 'px', height: cHeight + 'px', background: background }\"\n          @touchstart=\"_touchStart\"\n          @touchmove=\"_touchMove\"\n          @touchend=\"_touchEnd\"\n          :disable-scroll=\"disScroll\"\n          @error=\"_error\"\n          v-if=\"showchart\"\n        />\n      </view>\n      <view v-if=\"!ontouch\" >\n        <canvas\n          :id=\"cid\"\n          :canvasId=\"cid\"\n          :style=\"{ width: cWidth + 'px', height: cHeight + 'px', background: background }\"\n          :disable-scroll=\"disScroll\"\n          @tap=\"_tap\"\n          @error=\"_error\"\n          v-if=\"showchart\"\n        />\n      </view>\n    </block>\n    <!-- #endif -->\n  </view>\n</template>\n\n<script>\nimport uCharts from '../../js_sdk/u-charts/u-charts.js';\nimport cfu from '../../js_sdk/u-charts/config-ucharts.js';\n// #ifdef APP-VUE || H5\nimport cfe from '../../js_sdk/u-charts/config-echarts.js';\n// #endif\n\nfunction deepCloneAssign(origin = {}, ...args) {\n  for (let i in args) {\n    for (let key in args[i]) {\n      if (args[i].hasOwnProperty(key)) {\n        origin[key] = args[i][key] && typeof args[i][key] === 'object' ? deepCloneAssign(Array.isArray(args[i][key]) ? [] : {}, origin[key], args[i][key]) : args[i][key];\n      }\n    }\n  }\n  return origin;\n}\n\nfunction formatterAssign(args,formatter) {\n  for (let key in args) {\n    if(args.hasOwnProperty(key) && args[key] !== null && typeof args[key] === 'object'){\n      formatterAssign(args[key],formatter)\n    }else if(key === 'format' && typeof args[key] === 'string'){\n      args['formatter'] = formatter[args[key]] ? formatter[args[key]] : undefined;\n    }\n  }\n  return args;\n}\n\n// 时间转换函数，为了匹配uniClinetDB读取出的时间与categories不同\nfunction getFormatDate(date) {\n\tvar seperator = \"-\";\n\tvar year = date.getFullYear();\n\tvar month = date.getMonth() + 1;\n\tvar strDate = date.getDate();\n\tif (month >= 1 && month <= 9) {\n\t\t\tmonth = \"0\" + month;\n\t}\n\tif (strDate >= 0 && strDate <= 9) {\n\t\t\tstrDate = \"0\" + strDate;\n\t}\n\tvar currentdate = year + seperator + month + seperator + strDate;\n\treturn currentdate;\n}\n\nvar lastMoveTime = null;\n/**\n * 防抖\n *\n * @param { Function } fn 要执行的方法\n * @param { Number } wait  防抖多少毫秒\n *\n * 在 vue 中使用（注意：不能使用箭头函数，否则this指向不对，并且不能再次封装如：\n * move(){  // 错误调用方式\n *   debounce(function () {\n *   console.log(this.title);\n * }, 1000)}）;\n * 应该直接使用：// 正确调用方式\n * move: debounce(function () {\n *   console.log(this.title);\n * }, 1000)\n */\nfunction debounce(fn, wait) {\n  let timer = false;\n  return function() {\n    clearTimeout(timer);\n    timer && clearTimeout(timer);\n    timer = setTimeout(() => {\n      timer = false;\n      fn.apply(this, arguments); // 把参数传进去\n    }, wait);\n  };\n}\n\nexport default {\n  name: 'qiun-data-charts',\n  mixins: [uniCloud.mixinDatacom],\n  props: {\n    type: {\n      type: String,\n      default: null\n    },\n    canvasId: {\n      type: String,\n      default: 'uchartsid'\n    },\n    canvas2d: {\n      type: Boolean,\n      default: false\n    },\n    background: {\n      type: String,\n      default: 'rgba(0,0,0,0)'\n    },\n    animation: {\n      type: Boolean,\n      default: true\n    },\n    chartData: {\n      type: Object,\n      default() {\n        return {\n          categories: [],\n          series: []\n        };\n      }\n    },\n    opts: {\n      type: Object,\n      default() {\n        return {};\n      }\n    },\n    eopts: {\n      type: Object,\n      default() {\n        return {};\n      }\n    },\n    loadingType: {\n      type: Number,\n      default: 2\n    },\n    errorShow: {\n      type: Boolean,\n      default: true\n    },\n    errorReload: {\n      type: Boolean,\n      default: true\n    },\n    errorMessage: {\n      type: String,\n      default: null\n    },\n    inScrollView: {\n      type: Boolean,\n      default: false\n    },\n    reshow: {\n      type: Boolean,\n      default: false\n    },\n    reload: {\n      type: Boolean,\n      default: false\n    },\n    disableScroll: {\n      type: Boolean,\n      default: false\n    },\n    optsWatch: {\n      type: Boolean,\n      default: true\n    },\n    onzoom: {\n      type: Boolean,\n      default: false\n    },\n    ontap: {\n      type: Boolean,\n      default: true\n    },\n    ontouch: {\n      type: Boolean,\n      default: false\n    },\n    onmouse: {\n      type: Boolean,\n      default: true\n    },\n    onmovetip: {\n      type: Boolean,\n      default: false\n    },\n    echartsH5: {\n      type: Boolean,\n      default: false\n    },\n    echartsApp: {\n      type: Boolean,\n      default: false\n    },\n    tooltipShow: {\n      type: Boolean,\n      default: true\n    },\n    tooltipFormat: {\n      type: String,\n      default: undefined\n    },\n    tooltipCustom: {\n      type: Object,\n      default: undefined\n    },\n    startDate: {\n      type: String,\n      default: undefined\n    },\n    endDate: {\n      type: String,\n      default: undefined\n    },\n    textEnum: {\n      type: Array,\n      default () {\n        return []\n      }\n    },\n    groupEnum: {\n      type: Array,\n      default () {\n        return []\n      }\n    },\n    pageScrollTop: {\n      type: Number,\n      default: 0\n    },\n    directory: {\n      type: String,\n      default: '/'\n    },\n    tapLegend: {\n      type: Boolean,\n      default: true\n    },\n    menus: {\n      type: Array,\n      default () {\n        return []\n      }\n    }\n  },\n  data() {\n    return {\n      cid: 'uchartsid',\n      inWx: false,\n      inAli: false,\n      inTt: false,\n      inBd: false,\n      inH5: false,\n      inApp: false,\n      inWin: false,\n      type2d: true,\n      disScroll: false,\n      openmouse: false,\n      pixel: 1,\n      cWidth: 375,\n      cHeight: 250,\n      showchart: false,\n      echarts: false,\n      echartsResize:false,\n      uchartsOpts: {},\n      echartsOpts: {},\n      drawData:{},\n      lastDrawTime:null,\n    };\n  },\n  created(){\n    this.cid = this.canvasId\n    if (this.canvasId == 'uchartsid' || this.canvasId == '') {\n      let t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\n      let len = t.length\n      let id = ''\n      for (let i = 0; i < 32; i++) {\n        id += t.charAt(Math.floor(Math.random() * len))\n      }\n      this.cid = id\n    }\n    const systemInfo = uni.getSystemInfoSync()\n    if(systemInfo.platform === 'windows' || systemInfo.platform === 'mac'){\n      this.inWin = true;\n    }\n    // #ifdef MP-WEIXIN\n    this.inWx = true;\n    if (this.canvas2d === false || systemInfo.platform === 'windows' || systemInfo.platform === 'mac') {\n      this.type2d = false;\n    }else{\n      this.type2d = true;\n      this.pixel = systemInfo.pixelRatio;\n    }\n    // #endif\n    //非微信小程序端强制关闭canvas2d模式\n    // #ifndef MP-WEIXIN\n    this.type2d = false;\n    // #endif\n    // #ifdef  MP-TOUTIAO || MP-LARK || MP-ALIPAY\n    this.type2d = this.canvas2d;\n    // #endif\n    // #ifdef MP-ALIPAY\n    this.inAli = true;\n    this.pixel = systemInfo.pixelRatio;\n    // #endif\n    // #ifdef MP-BAIDU\n    this.inBd = true;\n    // #endif\n    // #ifdef MP-TOUTIAO\n    this.inTt = true;\n    // #endif\n    this.disScroll = this.disableScroll;\n  },\n  mounted() {\n    // #ifdef APP-VUE\n    this.inApp = true;\n    if (this.echartsApp === true) {\n      this.echarts = true;\n      this.openmouse = false;\n    }\n    // #endif\n    // #ifdef APP-NVUE\n    this.inApp = true;\n    this.mixinDatacomLoading = false\n    this.mixinDatacomErrorMessage = \"暂不支持NVUE\"\n    // #endif\n    // #ifdef H5\n    this.inH5 = true;\n    if(this.inWin === true){\n      this.openmouse = this.onmouse;\n    }\n    if (this.echartsH5 === true) {\n      this.echarts = true;\n    }\n    // #endif\n    this.$nextTick(()=>{\n      this.beforeInit();\n    })\n    // #ifndef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO || APP-VUE\n    const time = this.inH5 ? 500 : 200;\n    const _this = this;\n    uni.onWindowResize(\n      debounce(function(res) {\n        if (_this.mixinDatacomLoading == true) {\n          return;\n        }\n        let errmsg = _this.mixinDatacomErrorMessage;\n        if (errmsg !== null && errmsg !== 'null' && errmsg !== '') {\n          return;\n        }\n        if (_this.echarts) {\n          _this.echartsResize = !_this.echartsResize;\n        } else {\n          _this.resizeHandler();\n        }\n      }, time)\n    );\n    // #endif\n  },\n  destroyed(){\n    if(this.echarts === true){\n      delete cfe.option[this.cid]\n      delete cfe.instance[this.cid]\n    }else{\n      delete cfu.option[this.cid]\n      delete cfu.instance[this.cid]\n    }\n    // #ifndef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO\n    uni.offWindowResize(()=>{})\n    // #endif\n  },\n  watch: {\n    chartDataProps: {\n      handler(val, oldval) {\n        if (typeof val === 'object') {\n          if (JSON.stringify(val) !== JSON.stringify(oldval)) {\n            this._clearChart();\n            if (val.series && val.series.length > 0) {\n              this.beforeInit();\n            }else{\n              this.mixinDatacomLoading = true;\n              this.showchart = false;\n              this.mixinDatacomErrorMessage = null;\n            }\n          }\n        } else {\n          this.mixinDatacomLoading = false;\n          this._clearChart();\n          this.showchart = false;\n          this.mixinDatacomErrorMessage = '参数错误：chartData数据类型错误';\n        }\n      },\n      immediate: false,\n      deep: true\n    },\n    localdata:{\n      handler(val, oldval) {\n        if (JSON.stringify(val) !== JSON.stringify(oldval)) {\n          if (val.length > 0) {\n            this.beforeInit();\n          }else{\n            this.mixinDatacomLoading = true;\n            this._clearChart();\n            this.showchart = false;\n            this.mixinDatacomErrorMessage = null;\n          }\n        }\n      },\n      immediate: false,\n      deep: true\n    },\n    optsProps: {\n      handler(val, oldval) {\n        if (typeof val === 'object') {\n          if (JSON.stringify(val) !== JSON.stringify(oldval) && this.echarts === false && this.optsWatch == true) {\n            this.checkData(this.drawData);\n          }\n        } else {\n          this.mixinDatacomLoading = false;\n          this._clearChart();\n          this.showchart = false;\n          this.mixinDatacomErrorMessage = '参数错误：opts数据类型错误';\n        }\n      },\n      immediate: false,\n      deep: true\n    },\n    eoptsProps: {\n      handler(val, oldval) {\n        if (typeof val === 'object') {\n          if (JSON.stringify(val) !== JSON.stringify(oldval) && this.echarts === true) {\n            this.checkData(this.drawData);\n          }\n        } else {\n          this.mixinDatacomLoading = false;\n          this.showchart = false;\n          this.mixinDatacomErrorMessage = '参数错误：eopts数据类型错误';\n        }\n      },\n      immediate: false,\n      deep: true\n    },\n    reshow(val, oldval) {\n      if (val === true && this.mixinDatacomLoading === false) {\n        setTimeout(() => {\n          this.mixinDatacomErrorMessage = null;\n          this.echartsResize = !this.echartsResize;\n          this.checkData(this.drawData);\n        }, 200);\n      }\n    },\n    reload(val, oldval) {\n      if (val === true) {\n        this.showchart = false;\n        this.mixinDatacomErrorMessage = null;\n        this.reloading();\n      }\n    },\n    mixinDatacomErrorMessage(val, oldval) {\n      if (val) {\n        this.emitMsg({name: 'error', params: {type:\"error\", errorShow: this.errorShow, msg: val, id: this.cid}});\n        if(this.errorShow){\n          console.log('[秋云图表组件]' + val);\n        }\n      }\n    },\n    errorMessage(val, oldval) {\n      if (val && this.errorShow && val !== null && val !== 'null' && val !== '') {\n        this.showchart = false;\n        this.mixinDatacomLoading = false;\n        this.mixinDatacomErrorMessage = val;\n      } else {\n        this.showchart = false;\n        this.mixinDatacomErrorMessage = null;\n        this.reloading();\n      }\n    }\n  },\n  computed: {\n    optsProps() {\n      return JSON.parse(JSON.stringify(this.opts));\n    },\n    eoptsProps() {\n      return JSON.parse(JSON.stringify(this.eopts));\n    },\n    chartDataProps() {\n      return JSON.parse(JSON.stringify(this.chartData));\n    },\n  },\n  methods: {\n    beforeInit(){\n      this.mixinDatacomErrorMessage = null;\n      if (typeof this.chartData === 'object' && this.chartData != null && this.chartData.series !== undefined && this.chartData.series.length > 0) {\n        //拷贝一下chartData，为了opts变更后统一数据来源\n        this.drawData = deepCloneAssign({}, this.chartData);\n        this.mixinDatacomLoading = false;\n        this.showchart = true;\n        this.checkData(this.chartData);\n      }else if(this.localdata.length>0){\n        this.mixinDatacomLoading = false;\n        this.showchart = true;\n        this.localdataInit(this.localdata);\n      }else if(this.collection !== ''){\n        this.mixinDatacomLoading = false;\n        this.getCloudData();\n      }else{\n        this.mixinDatacomLoading = true;\n      }\n    },\n    localdataInit(resdata){\n      //替换enum类型为正确的描述\n      if(this.groupEnum.length>0){\n        for (let i = 0; i < resdata.length; i++) {\n          for (let j = 0; j < this.groupEnum.length; j++) {\n            if(resdata[i].group === this.groupEnum[j].value){\n              resdata[i].group = this.groupEnum[j].text\n            }\n          }\n        }\n      }\n      if(this.textEnum.length>0){\n        for (let i = 0; i < resdata.length; i++) {\n          for (let j = 0; j < this.textEnum.length; j++) {\n            if(resdata[i].text === this.textEnum[j].value){\n              resdata[i].text = this.textEnum[j].text\n            }\n          }\n        }\n      }\n      let needCategories = false;\n      let tmpData = {categories:[], series:[]}\n      let tmpcategories = []\n      let tmpseries = [];\n      //拼接categories\n      if(this.echarts === true){\n        needCategories = cfe.categories.includes(this.type)\n      }else{\n        needCategories = cfu.categories.includes(this.type)\n      }\n      if(needCategories === true){\n        //如果props中的chartData带有categories，则优先使用chartData的categories\n        if(this.chartData && this.chartData.categories && this.chartData.categories.length>0){\n          tmpcategories = this.chartData.categories\n        }else{\n          //如果是日期类型的数据，不管是本地数据还是云数据，都按起止日期自动拼接categories\n          if(this.startDate && this.endDate){\n            let idate = new Date(this.startDate)\n            let edate = new Date(this.endDate)\n            while (idate <= edate) {\n            \ttmpcategories.push(getFormatDate(idate))\n            \tidate = idate.setDate(idate.getDate() + 1)\n            \tidate = new Date(idate)\n            }\n          //否则从结果中去重并拼接categories\n          }else{\n            let tempckey = {};\n            resdata.map(function(item, index) {\n              if (item.text != undefined && !tempckey[item.text]) {\n                tmpcategories.push(item.text)\n                tempckey[item.text] = true\n              }\n            });\n          }\n        }\n        tmpData.categories = tmpcategories\n      }\n      //拼接series\n      let tempskey = {};\n      resdata.map(function(item, index) {\n        if (item.group != undefined && !tempskey[item.group]) {\n          tmpseries.push({ name: item.group, data: [] });\n          tempskey[item.group] = true;\n        }\n      });\n      //如果没有获取到分组名称(可能是带categories的数据，也可能是不带的饼图类)\n      if (tmpseries.length == 0) {\n        tmpseries = [{ name: '默认分组', data: [] }];\n        //如果是需要categories的图表类型\n        if(needCategories === true){\n          for (let j = 0; j < tmpcategories.length; j++) {\n            let seriesdata = 0;\n            for (let i = 0; i < resdata.length; i++) {\n              if (resdata[i].text == tmpcategories[j]) {\n                seriesdata = resdata[i].value;\n              }\n            }\n            tmpseries[0].data.push(seriesdata);\n          }\n        //如果是饼图类的图表类型\n        }else{\n          for (let i = 0; i < resdata.length; i++) {\n            tmpseries[0].data.push({\"name\": resdata[i].text,\"value\": resdata[i].value});\n          }\n        }\n      //如果有分组名\n      } else {\n        for (let k = 0; k < tmpseries.length; k++) {\n          //如果有categories\n          if (tmpcategories.length > 0) {\n            for (let j = 0; j < tmpcategories.length; j++) {\n              let seriesdata = 0;\n              for (let i = 0; i < resdata.length; i++) {\n                if (tmpseries[k].name == resdata[i].group && resdata[i].text == tmpcategories[j]) {\n                  seriesdata = resdata[i].value;\n                }\n              }\n              tmpseries[k].data.push(seriesdata);\n            }\n          //如果传了group而没有传text，即没有categories（正常情况下这种数据是不符合数据要求规范的）\n          } else {\n            for (let i = 0; i < resdata.length; i++) {\n              if (tmpseries[k].name == resdata[i].group) {\n                tmpseries[k].data.push(resdata[i].value);\n              }\n            }\n          }\n        }\n      }\n      tmpData.series = tmpseries\n      //拷贝一下chartData，为了opts变更后统一数据来源\n      this.drawData = deepCloneAssign({}, tmpData);\n      this.checkData(tmpData)\n    },\n    reloading() {\n      if(this.errorReload === false){\n        return;\n      }\n      this.showchart = false;\n      this.mixinDatacomErrorMessage = null;\n      if (this.collection !== '') {\n        this.mixinDatacomLoading = false;\n        this.onMixinDatacomPropsChange(true);\n      } else {\n        this.beforeInit();\n      }\n    },\n    checkData(anyData) {\n      let cid = this.cid\n      //复位opts或eopts\n      if(this.echarts === true){\n        cfe.option[cid] = deepCloneAssign({}, this.eopts);\n        cfe.option[cid].id = cid;\n        cfe.option[cid].type = this.type;\n      }else{\n        if (this.type && cfu.type.includes(this.type)) {\n          cfu.option[cid] = deepCloneAssign({}, cfu[this.type], this.opts);\n          cfu.option[cid].canvasId = cid;\n        } else {\n          this.mixinDatacomLoading = false;\n          this.showchart = false;\n          this.mixinDatacomErrorMessage = '参数错误：props参数中type类型不正确';\n        }\n      }\n      //挂载categories和series\n      let newData = deepCloneAssign({}, anyData);\n      if (newData.series !== undefined && newData.series.length > 0) {\n        this.mixinDatacomErrorMessage = null;\n        if (this.echarts === true) {\n          cfe.option[cid].chartData = newData;\n          this.$nextTick(()=>{\n            this.init()\n          })\n        }else{\n          cfu.option[cid].categories = newData.categories;\n          cfu.option[cid].series = newData.series;\n          this.$nextTick(()=>{\n            this.init()\n          })\n        }\n      }\n    },\n    resizeHandler() {\n      //渲染防抖\n      let currTime = Date.now();\n      let lastDrawTime = this.lastDrawTime?this.lastDrawTime:currTime-3000;\n      let duration = currTime - lastDrawTime;\n      if (duration < 1000) return;\n      let chartdom = uni\n        .createSelectorQuery()\n        // #ifndef MP-ALIPAY\n        .in(this)\n        // #endif\n        .select('#ChartBoxId'+this.cid)\n        .boundingClientRect(data => {\n          this.showchart = true;\n          if (data.width > 0 && data.height > 0) {\n            if (data.width !== this.cWidth || data.height !== this.cHeight) {\n              this.checkData(this.drawData)\n            }\n          }\n        })\n        .exec();\n    },\n    getCloudData() {\n      if (this.mixinDatacomLoading == true) {\n        return;\n      }\n      this.mixinDatacomLoading = true;\n      this.mixinDatacomGet()\n        .then(res => {\n          this.mixinDatacomResData = res.result.data;\n          this.localdataInit(this.mixinDatacomResData);\n        })\n        .catch(err => {\n          this.mixinDatacomLoading = false;\n          this.showchart = false;\n          this.mixinDatacomErrorMessage = '请求错误：' + err;\n        });\n    },\n    onMixinDatacomPropsChange(needReset, changed) {\n      if (needReset == true && this.collection !== '') {\n        this.showchart = false;\n        this.mixinDatacomErrorMessage = null;\n        this._clearChart();\n        this.getCloudData();\n      }\n    },\n    _clearChart() {\n      let cid = this.cid\n      if (this.echrts !== true && cfu.option[cid] && cfu.option[cid].context) {\n        const ctx = cfu.option[cid].context;\n        if(typeof ctx === \"object\" && !cfu.option[cid].update){\n          ctx.clearRect(0, 0, this.cWidth, this.cHeight);\n          ctx.draw();\n        }\n      }\n    },\n    init() {\n      let cid = this.cid\n      let chartdom = uni\n        .createSelectorQuery()\n        // #ifndef MP-ALIPAY\n        .in(this)\n        // #endif\n        .select('#ChartBoxId'+cid)\n        .boundingClientRect(data => {\n          if (data.width > 0 && data.height > 0) {\n            this.mixinDatacomLoading = false;\n            this.showchart = true;\n            this.lastDrawTime = Date.now();\n            this.cWidth = data.width;\n            this.cHeight = data.height;\n            if(this.echarts !== true){\n              cfu.option[cid].background = this.background == 'rgba(0,0,0,0)' ? '#FFFFFF' : this.background;\n              cfu.option[cid].canvas2d = this.type2d;\n              cfu.option[cid].pixelRatio = this.pixel;\n              cfu.option[cid].animation = this.animation;\n              cfu.option[cid].width = data.width * this.pixel;\n              cfu.option[cid].height = data.height * this.pixel;\n              cfu.option[cid].onzoom = this.onzoom;\n              cfu.option[cid].ontap = this.ontap;\n              cfu.option[cid].ontouch = this.ontouch;\n              cfu.option[cid].onmouse = this.openmouse;\n              cfu.option[cid].onmovetip = this.onmovetip;\n              cfu.option[cid].tooltipShow = this.tooltipShow;\n              cfu.option[cid].tooltipFormat = this.tooltipFormat;\n              cfu.option[cid].tooltipCustom = this.tooltipCustom;\n              cfu.option[cid].inScrollView = this.inScrollView;\n              cfu.option[cid].lastDrawTime = this.lastDrawTime;\n              cfu.option[cid].tapLegend = this.tapLegend;\n            }\n            //如果是H5或者App端，采用renderjs渲染图表\n            if (this.inH5 || this.inApp) {\n              if (this.echarts == true) {\n                cfe.option[cid].ontap = this.ontap;\n                cfe.option[cid].onmouse = this.openmouse;\n                cfe.option[cid].tooltipShow = this.tooltipShow;\n                cfe.option[cid].tooltipFormat = this.tooltipFormat;\n                cfe.option[cid].tooltipCustom = this.tooltipCustom;\n                cfe.option[cid].lastDrawTime = this.lastDrawTime;\n                this.echartsOpts = deepCloneAssign({}, cfe.option[cid]);\n              } else {\n                cfu.option[cid].rotateLock = cfu.option[cid].rotate;\n                this.uchartsOpts = deepCloneAssign({}, cfu.option[cid]);\n              }\n            //如果是小程序端，采用uCharts渲染\n            } else {\n              cfu.option[cid] = formatterAssign(cfu.option[cid],cfu.formatter)\n              this.mixinDatacomErrorMessage = null;\n              this.mixinDatacomLoading = false;\n              this.showchart = true;\n              this.$nextTick(()=>{\n                if (this.type2d === true) {\n                  const query = uni.createSelectorQuery().in(this)\n                  query\n                    .select('#' + cid)\n                    .fields({ node: true, size: true })\n                    .exec(res => {\n                      if (res[0]) {\n                        const canvas = res[0].node;\n                        const ctx = canvas.getContext('2d');\n                        cfu.option[cid].context = ctx;\n                        cfu.option[cid].rotateLock = cfu.option[cid].rotate;\n                        if(cfu.instance[cid] && cfu.option[cid] && cfu.option[cid].update === true){\n                          this._updataUChart(cid)\n                        }else{\n                          canvas.width = data.width * this.pixel;\n                          canvas.height = data.height * this.pixel;\n                          canvas._width = data.width * this.pixel;\n                          canvas._height = data.height * this.pixel;\n                          setTimeout(()=>{\n                            cfu.option[cid].context.restore();\n                            cfu.option[cid].context.save();\n                            this._newChart(cid)\n                          },100)\n                        }\n                      } else {\n                        this.showchart = false;\n                        this.mixinDatacomErrorMessage = '参数错误：开启2d模式后，未获取到dom节点，canvas-id:' + cid;\n                      }\n                    });\n                } else {\n                  if(this.inAli){\n                    cfu.option[cid].rotateLock = cfu.option[cid].rotate;\n                  }\n                  cfu.option[cid].context = uni.createCanvasContext(cid, this);\n                  if(cfu.instance[cid] && cfu.option[cid] && cfu.option[cid].update === true){\n                    this._updataUChart(cid)\n                  }else{\n                    setTimeout(()=>{\n                      cfu.option[cid].context.restore();\n                      cfu.option[cid].context.save();\n                      this._newChart(cid)\n                    },100)\n                  }\n                }\n              })\n            }\n          } else {\n            this.mixinDatacomLoading = false;\n            this.showchart = false;\n            if (this.reshow == true) {\n              this.mixinDatacomErrorMessage = '布局错误：未获取到父元素宽高尺寸！canvas-id:' + cid;\n            }\n          }\n        })\n        .exec();\n    },\n    saveImage(){\n    \tuni.canvasToTempFilePath({\n    \t  canvasId: this.cid,\n    \t  success: res=>{\n    \t    //#ifdef H5\n    \t\t\tvar a = document.createElement(\"a\");\n    \t\t\ta.href = res.tempFilePath;\n    \t\t\ta.download = this.cid;\n    \t\t\ta.target = '_blank'\n    \t\t\ta.click();\n    \t    //#endif\n    \t    //#ifndef H5\n    \t      uni.saveImageToPhotosAlbum({\n              filePath: res.tempFilePath,\n              success: function () {\n                uni.showToast({\n                  title: '保存成功',\n                  duration: 2000\n                });\n              }\n    \t      });\n    \t    //#endif\n    \t  } \n    \t},this);\n    },\n    getImage(){\n      if(this.type2d == false){\n        uni.canvasToTempFilePath({\n          canvasId: this.cid,\n          success: res=>{\n            this.emitMsg({name: 'getImage', params: {type:\"getImage\", base64: res.tempFilePath}});\n          }\n        },this);\n      }else{\n        const query = uni.createSelectorQuery().in(this)\n        query\n          .select('#' + this.cid)\n          .fields({ node: true, size: true })\n          .exec(res => {\n            if (res[0]) {\n              const canvas = res[0].node;\n              this.emitMsg({name: 'getImage', params: {type:\"getImage\", base64: canvas.toDataURL('image/png')}});\n            }\n          });\n      }\n    },\n    // #ifndef APP-VUE || H5\n    _newChart(cid) {\n      if (this.mixinDatacomLoading == true) {\n        return;\n      }\n      this.showchart = true;\n      cfu.instance[cid] = new uCharts(cfu.option[cid]);\n      cfu.instance[cid].addEventListener('renderComplete', () => {\n        this.emitMsg({name: 'complete', params: {type:\"complete\", complete: true, id: cid}});\n        cfu.instance[cid].delEventListener('renderComplete')\n      });\n      cfu.instance[cid].addEventListener('scrollLeft', () => {\n        this.emitMsg({name: 'scrollLeft', params: {type:\"scrollLeft\", scrollLeft: true, id: cid}});\n      });\n      cfu.instance[cid].addEventListener('scrollRight', () => {\n        this.emitMsg({name: 'scrollRight', params: {type:\"scrollRight\", scrollRight: true, id: cid}});\n      });\n    },\n    _updataUChart(cid) {\n      cfu.instance[cid].updateData(cfu.option[cid])\n    },\n    _tooltipDefault(item, category, index, opts) {\n      if (category) {\n        let data = item.data\n        if(typeof item.data === \"object\"){\n          data = item.data.value\n        }\n        return category + ' ' + item.name + ':' + data;\n      } else {\n        if (item.properties && item.properties.name) {\n          return item.properties.name;\n        } else {\n          return item.name + ':' + item.data;\n        }\n      }\n    },\n    _showTooltip(e) {\n      let cid = this.cid\n      let tc = cfu.option[cid].tooltipCustom\n      if (tc && tc !== undefined && tc !== null) {\n        let offset = undefined;\n        if (tc.x >= 0 && tc.y >= 0) {\n          offset = { x: tc.x, y: tc.y + 10 };\n        }\n        cfu.instance[cid].showToolTip(e, {\n          index: tc.index,\n          offset: offset,\n          textList: tc.textList,\n          formatter: (item, category, index, opts) => {\n            if (typeof cfu.option[cid].tooltipFormat === 'string' && cfu.formatter[cfu.option[cid].tooltipFormat]) {\n              return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);\n            } else {\n              return this._tooltipDefault(item, category, index, opts);\n            }\n          }\n        });\n      } else {\n        cfu.instance[cid].showToolTip(e, {\n          formatter: (item, category, index, opts) => {\n            if (typeof cfu.option[cid].tooltipFormat === 'string' && cfu.formatter[cfu.option[cid].tooltipFormat]) {\n              return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);\n            } else {\n              return this._tooltipDefault(item, category, index, opts);\n            }\n          }\n        });\n      }\n    },\n    _tap(e,move) {\n      let cid = this.cid\n      let currentIndex = null;\n      let legendIndex = null;\n      if (this.inScrollView === true || this.inAli) {\n        let chartdom = uni\n          .createSelectorQuery()\n          // #ifndef MP-ALIPAY\n          .in(this)\n          .select('#ChartBoxId'+cid)\n          // #endif\n          // #ifdef MP-ALIPAY\n          .select('#'+this.cid)\n          // #endif\n          .boundingClientRect(data => {\n            e.changedTouches=[];\n            if (this.inAli) {\n              e.changedTouches.unshift({ x: e.detail.clientX - data.left, y: e.detail.clientY - data.top});\n            }else{\n              e.changedTouches.unshift({ x: e.detail.x - data.left, y: e.detail.y - data.top - this.pageScrollTop});\n            }\n            if(move){\n              if (this.tooltipShow === true) {\n                this._showTooltip(e);\n              }\n            }else{\n              currentIndex = cfu.instance[cid].getCurrentDataIndex(e);\n              legendIndex = cfu.instance[cid].getLegendDataIndex(e);\n              if(this.tapLegend === true){\n                cfu.instance[cid].touchLegend(e);\n              }\n              if (this.tooltipShow === true) {\n                this._showTooltip(e);\n              }\n              this.emitMsg({name: 'getIndex', params: { type:\"getIndex\", event:{ x: e.detail.x - data.left, y: e.detail.y - data.top }, currentIndex: currentIndex, legendIndex: legendIndex, id: cid, opts: cfu.instance[cid].opts}});\n            }\n          })\n          .exec();\n      } else {\n        if(move){\n          if (this.tooltipShow === true) {\n            this._showTooltip(e);\n          }\n        }else{\n          e.changedTouches=[];\n          e.changedTouches.unshift({ x: e.detail.x - e.currentTarget.offsetLeft, y: e.detail.y - e.currentTarget.offsetTop });\n          currentIndex = cfu.instance[cid].getCurrentDataIndex(e);\n          legendIndex = cfu.instance[cid].getLegendDataIndex(e);\n          if(this.tapLegend === true){\n            cfu.instance[cid].touchLegend(e);\n          }\n          if (this.tooltipShow === true) {\n            this._showTooltip(e);\n          }\n          this.emitMsg({name: 'getIndex', params: {type:\"getIndex\", event:{ x: e.detail.x, y: e.detail.y - e.currentTarget.offsetTop }, currentIndex: currentIndex, legendIndex: legendIndex, id: cid, opts: cfu.instance[cid].opts}});\n        }\n      }\n    },\n    _touchStart(e) {\n      let cid = this.cid\n      lastMoveTime=Date.now();\n      if(cfu.option[cid].enableScroll === true && e.touches.length == 1){\n        cfu.instance[cid].scrollStart(e);\n      }\n      this.emitMsg({name:'getTouchStart', params:{type:\"touchStart\", event:e.changedTouches[0], id:cid}});\n    },\n    _touchMove(e) {\n      let cid = this.cid\n      let currMoveTime = Date.now();\n      let duration = currMoveTime - lastMoveTime;\n      let touchMoveLimit = cfu.option[cid].touchMoveLimit || 24;\n      if (duration < Math.floor(1000 / touchMoveLimit)) return;//每秒60帧\n      lastMoveTime = currMoveTime;\n      if(cfu.option[cid].enableScroll === true && e.changedTouches.length == 1){\n        cfu.instance[cid].scroll(e);\n      }\n      if(this.ontap === true && cfu.option[cid].enableScroll === false && this.onmovetip === true){\n        this._tap(e,true)\n      }\n      if(this.ontouch === true && cfu.option[cid].enableScroll === true && this.onzoom === true && e.changedTouches.length == 2){\n        cfu.instance[cid].dobuleZoom(e);\n      }\n      this.emitMsg({name: 'getTouchMove', params: {type:\"touchMove\", event:e.changedTouches[0], id: cid}});\n    },\n    _touchEnd(e) {\n      let cid = this.cid\n      if(cfu.option[cid].enableScroll === true && e.touches.length == 0){\n        cfu.instance[cid].scrollEnd(e);\n      }\n      this.emitMsg({name:'getTouchEnd', params:{type:\"touchEnd\", event:e.changedTouches[0], id:cid}});\n      if(this.ontap === true && cfu.option[cid].enableScroll === false && this.onmovetip === true){\n        this._tap(e,true)\n      }\n    },\n    // #endif\n    _error(e) {\n      this.mixinDatacomErrorMessage = e.detail.errMsg;\n    },\n    emitMsg(msg) {\n      this.$emit(msg.name, msg.params);\n    },\n    getRenderType() {\n      //防止如果开启echarts且父元素为v-if的情况renderjs监听不到prop变化的问题\n      if(this.echarts===true && this.mixinDatacomLoading===false){\n        this.beforeInit()\n      }\n    },\n    toJSON(){\n      return this\n    }\n  }\n};\n</script>\n\n<!-- #ifdef APP-VUE || H5 -->\n<script module=\"rdcharts\" lang=\"renderjs\">\nimport uChartsRD from '../../js_sdk/u-charts/u-charts.js';\nimport cfu from '../../js_sdk/u-charts/config-ucharts.js';\nimport cfe from '../../js_sdk/u-charts/config-echarts.js';\n\nvar that = {};\nvar rootdom = null;\n\nfunction rddeepCloneAssign(origin = {}, ...args) {\n  for (let i in args) {\n    for (let key in args[i]) {\n      if (args[i].hasOwnProperty(key)) {\n        origin[key] = args[i][key] && typeof args[i][key] === 'object' ? rddeepCloneAssign(Array.isArray(args[i][key]) ? [] : {}, origin[key], args[i][key]) : args[i][key];\n      }\n    }\n  }\n  return origin;\n}\n\nfunction rdformatterAssign(args,formatter) {\n  for (let key in args) {\n    if(args.hasOwnProperty(key) && args[key] !== null && typeof args[key] === 'object'){\n      rdformatterAssign(args[key],formatter)\n    }else if(key === 'format' && typeof args[key] === 'string'){\n      args['formatter'] = formatter[args[key]] ? formatter[args[key]] : undefined;\n    }\n  }\n  return args;\n}\n\nexport default {\n  data() {\n    return {\n      rid:null\n    }\n  },\n  mounted() {\n    rootdom = {top:0,left:0}\n    // #ifdef H5\n    let dm = document.querySelectorAll('uni-main')[0]\n    if(dm === undefined){\n      dm = document.querySelectorAll('uni-page-wrapper')[0]\n    }\n    rootdom = {top:dm.offsetTop,left:dm.offsetLeft}\n    // #endif\n    setTimeout(()=>{\n      if(this.rid === null){\n        this.$ownerInstance && this.$ownerInstance.callMethod('getRenderType')\n      }\n    },200)\n  },\n  destroyed(){\n    delete cfu.option[this.rid]\n    delete cfu.instance[this.rid]\n    delete cfe.option[this.rid]\n    delete cfe.instance[this.rid]\n  },\n  methods: {\n    //==============以下是ECharts的方法====================\n    ecinit(newVal, oldVal, owner, instance){\n      let cid = JSON.stringify(newVal.id)\n      this.rid = cid\n      that[cid] = this.$ownerInstance || instance\n      let eopts = JSON.parse(JSON.stringify(newVal))\n      let type = eopts.type;\n      //载入并覆盖默认配置\n      if (type && cfe.type.includes(type)) {\n        cfe.option[cid] = rddeepCloneAssign({}, cfe[type], eopts);\n      }else{\n        cfe.option[cid] = rddeepCloneAssign({}, eopts);\n      }\n      let newData = eopts.chartData;\n      if(newData){\n        //挂载categories和series\n        if(cfe.option[cid].xAxis && cfe.option[cid].xAxis.type && cfe.option[cid].xAxis.type === 'category'){\n          cfe.option[cid].xAxis.data = newData.categories\n        }\n        if(cfe.option[cid].yAxis && cfe.option[cid].yAxis.type && cfe.option[cid].yAxis.type === 'category'){\n          cfe.option[cid].yAxis.data = newData.categories\n        }\n        cfe.option[cid].series = []\n        for (var i = 0; i < newData.series.length; i++) {\n          cfe.option[cid].seriesTemplate = cfe.option[cid].seriesTemplate ? cfe.option[cid].seriesTemplate : {}\n          let Template = rddeepCloneAssign({},cfe.option[cid].seriesTemplate,newData.series[i])\n          cfe.option[cid].series.push(Template)\n        }\n      }\n      \n      if (typeof window.echarts === 'object') {\n          this.newEChart()\n      }else{\n        const script = document.createElement('script')\n        // #ifdef APP-VUE\n        script.src = './uni_modules/qiun-data-charts/static/app-plus/echarts.min.js'\n        // #endif\n        // #ifdef H5\n        const { origin, pathname } = window.location\n        const rooturl = origin + pathname\n        script.src = rooturl + 'uni_modules/qiun-data-charts/static/h5/echarts.min.js'\n        // #endif\n        script.onload = this.newEChart\n        document.head.appendChild(script)\n      }\n    },\n    ecresize(newVal, oldVal, owner, instance){\n      if(cfe.instance[this.rid]){\n        cfe.instance[this.rid].resize()\n      }\n    },\n    newEChart(){\n      let cid = this.rid\n      if(cfe.instance[cid] === undefined){\n        cfe.instance[cid] = echarts.init(that[cid].$el.children[0])\n        //ontap开启后才触发click事件\n        if(cfe.option[cid].ontap === true){\n          cfe.instance[cid].on('click', resdata => {\n            let event = JSON.parse(JSON.stringify({\n              x:resdata.event.offsetX,y:resdata.event.offsetY\n            }))\n            that[cid].callMethod('emitMsg',{name:\"getIndex\", params:{type:\"getIndex\", event:event, currentIndex:resdata.dataIndex, value:resdata.data, seriesName: resdata.seriesName,id:cid}})\n          })\n          // 增加ECharts的highlight消息，实现按下移动返回索引功能。add by onefish 创建于 2021-12-11 09:50\n          cfe.instance[cid].on('highlight', resdata => {\n            that[cid].callMethod('emitMsg',{name:\"getHighlight\", params:{type:\"highlight\", res:resdata, id:cid}})\n          })\n        }\n        this.updataEChart(cid,cfe.option[cid])\n      }else{\n        this.updataEChart(cid,cfe.option[cid])\n      }\n    },\n    updataEChart(cid,option){\n      //替换option内format属性为formatter的预定义方法\n      option = rdformatterAssign(option,cfe.formatter)\n      if(option.tooltip){\n        option.tooltip.show = option.tooltipShow?true:false;\n        option.tooltip.position = this.tooltipPosition()\n        //tooltipFormat方法，替换组件的tooltipFormat为config-echarts.js内对应的方法\n        if (typeof option.tooltipFormat === 'string' && cfe.formatter[option.tooltipFormat]) {\n          option.tooltip.formatter = option.tooltip.formatter ? option.tooltip.formatter : cfe.formatter[option.tooltipFormat]\n        }\n      }\n      // 颜色渐变添加的方法\n      if (option.series) {\n      \tfor (let i in option.series) {\n      \t\tlet linearGradient = option.series[i].linearGradient\n      \t\tif (linearGradient) {\n      \t\t\toption.series[i].color = new echarts.graphic.LinearGradient(linearGradient[0],linearGradient[1],linearGradient[2],linearGradient[3],linearGradient[4])\n      \t\t}\n      \t}\n      }\n      cfe.instance[cid].setOption(option, option.notMerge)\n      cfe.instance[cid].on('finished', function(){\n        that[cid].callMethod('emitMsg',{name:\"complete\",params:{type:\"complete\",complete:true,id:cid}})\n        if(cfe.instance[cid]){\n          cfe.instance[cid].off('finished')\n        }\n      })\n    },\n    tooltipPosition(){\n      return (point, params, dom, rect, size) => {\n      \tlet x = point[0]\n      \tlet y = point[1]\n      \tlet viewWidth = size.viewSize[0]\n      \tlet viewHeight = size.viewSize[1]\n      \tlet boxWidth = size.contentSize[0]\n      \tlet boxHeight = size.contentSize[1]\n      \tlet posX = x + 30 \n      \tlet posY = y + 30 \n      \tif (posX + boxWidth > viewWidth) { \n      \t\tposX = x - boxWidth - 30\n      \t}\n      \tif (posY + boxHeight > viewHeight) {\n      \t\tposY = y - boxHeight - 30\n      \t}\n      \treturn [posX, posY]\n      }\n    },\n    //==============以下是uCharts的方法====================\n    ucinit(newVal, oldVal, owner, instance){\n      if(JSON.stringify(newVal) == JSON.stringify(oldVal)){\n        return;\n      }\n      if(!newVal.canvasId){\n        return;\n      }\n      let cid = JSON.parse(JSON.stringify(newVal.canvasId))\n      this.rid = cid\n      that[cid] = this.$ownerInstance || instance\n      cfu.option[cid] = JSON.parse(JSON.stringify(newVal))\n      cfu.option[cid] = rdformatterAssign(cfu.option[cid],cfu.formatter)\n      let canvasdom = document.getElementById(cid)\n      if(canvasdom && canvasdom.children[0]){\n        cfu.option[cid].context = canvasdom.children[0].getContext(\"2d\")\n        if(cfu.instance[cid] && cfu.option[cid] && cfu.option[cid].update === true){\n          this.updataUChart()\n        }else{\n          setTimeout(()=>{\n            cfu.option[cid].context.restore();\n            cfu.option[cid].context.save();\n            this.newUChart()\n          },100)\n        }\n      }\n    },\n    newUChart() {\n      let cid = this.rid\n      cfu.instance[cid] = new uChartsRD(cfu.option[cid])\n      cfu.instance[cid].addEventListener('renderComplete', () => {\n        that[cid].callMethod('emitMsg',{name:\"complete\",params:{type:\"complete\",complete:true,id:cid}})\n        cfu.instance[cid].delEventListener('renderComplete')\n      });\n      cfu.instance[cid].addEventListener('scrollLeft', () => {\n        that[cid].callMethod('emitMsg',{name:\"scrollLeft\",params:{type:\"scrollLeft\",scrollLeft:true,id:cid}})\n      });\n      cfu.instance[cid].addEventListener('scrollRight', () => {\n        that[cid].callMethod('emitMsg',{name:\"scrollRight\",params:{type:\"scrollRight\",scrollRight:true,id:cid}})\n      });\n    },\n    updataUChart() {\n      let cid = this.rid\n      cfu.instance[cid].updateData(cfu.option[cid])\n    },\n    tooltipDefault(item, category, index, opts) {\n      if (category) {\n        let data = item.data\n        if(typeof item.data === \"object\"){\n          data = item.data.value\n        }\n        return category + ' ' + item.name + ':' + data;\n      } else {\n        if (item.properties && item.properties.name) {\n          return item.properties.name ;\n        } else {\n          return item.name + ':' + item.data;\n        }\n      }\n    },\n    showTooltip(e,cid) {\n      let tc = cfu.option[cid].tooltipCustom\n      if (tc && tc !== undefined && tc !== null) {\n        let offset = undefined;\n        if (tc.x >= 0 && tc.y >= 0) {\n          offset = { x: tc.x, y: tc.y + 10 };\n        }\n        cfu.instance[cid].showToolTip(e, {\n          index: tc.index,\n          offset: offset,\n          textList: tc.textList,\n          formatter: (item, category, index, opts) => {\n            if (typeof cfu.option[cid].tooltipFormat === 'string' && cfu.formatter[cfu.option[cid].tooltipFormat]) {\n              return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);\n            } else {\n              return this.tooltipDefault(item, category, index, opts);\n            }\n          }\n        });\n      } else {\n        cfu.instance[cid].showToolTip(e, {\n          formatter: (item, category, index, opts) => {\n            if (typeof cfu.option[cid].tooltipFormat === 'string' && cfu.formatter[cfu.option[cid].tooltipFormat]) {\n              return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);\n            } else {\n              return this.tooltipDefault(item, category, index, opts);\n            }\n          }\n        });\n      }\n    },\n    tap(e) {\n      let cid = this.rid\n      let ontap = cfu.option[cid].ontap\n      let tooltipShow = cfu.option[cid].tooltipShow\n      let tapLegend = cfu.option[cid].tapLegend\n      if(ontap == false) return;\n      let currentIndex=null\n      let legendIndex=null\n      let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()\n      let tmpe = {}\n      if(e.detail.x){//tap或者click的事件\n        tmpe = { x: e.detail.x - rchartdom.left, y:e.detail.y - rchartdom.top + rootdom.top}\n      }else{//mouse的事件\n        tmpe = { x: e.clientX - rchartdom.left, y:e.clientY - rchartdom.top + rootdom.top}\n      }\n      e.changedTouches = [];\n      e.changedTouches.unshift(tmpe)\n      currentIndex=cfu.instance[cid].getCurrentDataIndex(e)\n      legendIndex=cfu.instance[cid].getLegendDataIndex(e)\n      if(tapLegend === true){\n        cfu.instance[cid].touchLegend(e);\n      }\n      if(tooltipShow==true){\n        this.showTooltip(e,cid)\n      }\n      that[cid].callMethod('emitMsg',{name:\"getIndex\",params:{type:\"getIndex\",event:tmpe,currentIndex:currentIndex,legendIndex:legendIndex,id:cid, opts: cfu.instance[cid].opts}})\n    },\n    touchStart(e) {\n      let cid = this.rid\n      let ontouch = cfu.option[cid].ontouch\n      if(ontouch == false) return;\n      if(cfu.option[cid].enableScroll === true && e.touches.length == 1){\n        cfu.instance[cid].scrollStart(e);\n      }\n      that[cid].callMethod('emitMsg',{name:\"getTouchStart\",params:{type:\"touchStart\",event:e.changedTouches[0],id:cid}})\n    },\n    touchMove(e) {\n      let cid = this.rid\n      let ontouch = cfu.option[cid].ontouch\n      if(ontouch == false) return;\n      if(cfu.option[cid].enableScroll === true && e.changedTouches.length == 1){\n        cfu.instance[cid].scroll(e);\n      }\n      if(cfu.option[cid].ontap === true && cfu.option[cid].enableScroll === false && cfu.option[cid].onmovetip === true){\n        let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()\n        let tmpe = { x: e.changedTouches[0].clientX - rchartdom.left, y:e.changedTouches[0].clientY - rchartdom.top + rootdom.top}\n        e.changedTouches.unshift(tmpe)\n        if(cfu.option[cid].tooltipShow === true){\n          this.showTooltip(e,cid)\n        }\n      }\n      if(ontouch === true && cfu.option[cid].enableScroll === true && cfu.option[cid].onzoom === true && e.changedTouches.length == 2){\n        cfu.instance[cid].dobuleZoom(e);\n      }\n      that[cid].callMethod('emitMsg',{name:\"getTouchMove\",params:{type:\"touchMove\",event:e.changedTouches[0],id:cid}})\n    },\n    touchEnd(e) {\n      let cid = this.rid\n      let ontouch = cfu.option[cid].ontouch\n      if(ontouch == false) return;\n      if(cfu.option[cid].enableScroll === true && e.touches.length == 0){\n        cfu.instance[cid].scrollEnd(e);\n      }\n      that[cid].callMethod('emitMsg',{name:\"getTouchEnd\",params:{type:\"touchEnd\",event:e.changedTouches[0],id:cid}})\n    },\n    mouseDown(e) {\n      let cid = this.rid\n      let onmouse = cfu.option[cid].onmouse\n      if(onmouse == false) return;\n      let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()\n      let tmpe = {}\n      tmpe = { x: e.clientX - rchartdom.left, y:e.clientY - rchartdom.top + rootdom.top}\n      e.changedTouches = [];\n      e.changedTouches.unshift(tmpe)\n      cfu.instance[cid].scrollStart(e)\n      cfu.option[cid].mousedown=true;\n      that[cid].callMethod('emitMsg',{name:\"getTouchStart\",params:{type:\"mouseDown\",event:tmpe,id:cid}})\n    },\n    mouseMove(e) {\n      let cid = this.rid\n      let onmouse = cfu.option[cid].onmouse\n      let tooltipShow = cfu.option[cid].tooltipShow\n      if(onmouse == false) return;\n      let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()\n      let tmpe = {}\n      tmpe = { x: e.clientX - rchartdom.left, y:e.clientY - rchartdom.top + rootdom.top}\n      e.changedTouches = [];\n      e.changedTouches.unshift(tmpe)\n      if(cfu.option[cid].mousedown){\n        cfu.instance[cid].scroll(e)\n        that[cid].callMethod('emitMsg',{name:\"getTouchMove\",params:{type:\"mouseMove\",event:tmpe,id:cid}})\n      }else if(cfu.instance[cid]){\n        if(tooltipShow==true){\n          this.showTooltip(e,cid)\n        }\n      }\n    },\n    mouseUp(e) {\n      let cid = this.rid\n      let onmouse = cfu.option[cid].onmouse\n      if(onmouse == false) return;\n      let rchartdom = document.getElementById('UC'+cid).getBoundingClientRect()\n      let tmpe = {}\n      tmpe = { x: e.clientX - rchartdom.left, y:e.clientY - rchartdom.top + rootdom.top}\n      e.changedTouches = [];\n      e.changedTouches.unshift(tmpe)\n      cfu.instance[cid].scrollEnd(e)\n      cfu.option[cid].mousedown=false;\n      that[cid].callMethod('emitMsg',{name:\"getTouchEnd\",params:{type:\"mouseUp\",event:tmpe,id:cid}})\n    },\n  }\n}\n</script>\n<!-- #endif -->\n\n<style scoped>\n.chartsview {\n  width: 100%;\n  height: 100%;\n  display: flex;\n  flex: 1;\n  justify-content: center;\n  align-items: center;\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/components/qiun-error/qiun-error.vue",
    "content": "<template>\n\t<view class=\"chartsview\">\n\t\t<view class=\"charts-error\"></view>\n\t\t<view class=\"charts-font\">{{errorMessage==null?'请点击重试':errorMessage}}</view>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tname: 'qiun-error',\n\t\tprops: {\n\t\t\terrorMessage: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: null\n\t\t\t},\n\t\t},\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>\n\t.chartsview {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tflex: 1;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t}\n\t.charts-font{\n\t\tfont-size: 14px;\n\t\tcolor: #CCCCCC;\n\t\tmargin-top: 10px;\n\t}\n\t.charts-error{\n\t\twidth: 128px;\n\t\theight: 128px;\n\t\tbackground: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAUz0lEQVR4Xu1de3Bc1X3+zmp3jYVWfkPAgCWwY8zLEglNQCSW0yT9o2SQaDKdNulUykwfM+k09p+J3ImYIPJXi9yZzDSZZiwyaZuZBCwnNG1DMogEmUAAy7xs/MAyNhCMjWWtsK1deU/n23OvtLu6j3Pv3t29d71nxjOSde455/5+3/m9z7kCjRY4BR7eK7fkcmhrasLT37hbTAY+QYADigDHagwFYGhc7gZwHMAUgG4hMPzNe8RoWInTAECAnHl4r+yREt0DXWIbhx3cJ5fHP8TYjntFR4DTBDqUIwBmMrJDCtyPHDoQw0Q8jkeXinCLtECp43Gwh56R22IxTBbu+KFxOTbQJbo9DlW17rYASGdlNySeKl2JADpbkmKiait0mWhoXHZkmzE52CkocmvavvOsbMvl8MhAl+jlQrg2CQzu6BI9NV2Yw+T2AJiVo+DuL2kSeLQ1KfrC8kLcYbkYBv/pbjEWhjUNjUvSpk9KSAicm2tGXxjAaUcbewBkJAm6xeLBp1PJ2os06ttcDl8H0CEEaGnvGegSg2EAQZTW4B0AEntSS2ov0mhgJc5jmwT6IDEWi2E0zNZ2WEFhC4CZjCRxH7GwAfpbkmIkLC9EFQBg20BXeOySsNBGZx2OXkB6Vg5CgAbMZgD7BTDSkhTDOgM3+kSDAr7iANNz8n4hQRdxojUu9kTjVRurtKKAJwBIKZfPZPOuYWFgY6wlgV4hau+GNVjsnQKeAJDOSIp/Wt6lbSKeQG8jSOSdAbV+wisA7FxDSGAqBmwNS5DIiGIucyNwKiGedutTz3/3BgCb4JBJoGqDIJ2VW4REmxRog0S3lGgT/NlfY3RzCgJjQmJSCkxeDuDwBgCb8HAhvQkCIdCbSgQfmSPDmWGDzHsm1UqwjBEUAMbqERCeAEBGz2RknwR2uW0yAZQdL6DR+WEW90syXLmjtW8So0Jg9MoE9tSD4esZANUAAd1M5NAjGOULaaOkAzCKGEaj7Ar7AoBHEGxrSYqdOnxMZ+W3ZA59ZehxnWkC7yMlJkUMIy1x7IyaVPANAAMEHTlgTACO1rYERlqTot+J8nbp58C5VcEBDftnOEpAKAsAgYPAPgNZQbYFP3QeCAybJ/Bg2CVC2QDwCoJUAtudiJKuExCQLoZbPKirAoOHovuIThVByuXii2jE/C9I2TaXBYsfmThyahMtCWy1A4ERbj7rvvRI9aCa3F7pINm3n5XdXgtjFgHAYCQrW4v8bBo6MYFep5cwmEefuSwQpDNSRoq9+osdrqRaGBqXMhfDVi8gWASAdEbuswuyGCKNSLatBygXBHUqAQohMmHESAKrqzSro4TIS2yOq10dVQQAuyKQUoC7BXnIxHQWwwL4ay/qIM/8DHaFJuijv7M99QzaNmAx6hzQFsvhKSmxvakJo7oHUooA4MUA0wHBTDYfQnVUB6bFnLc1JHqiFgPwxPnSzhKjLUn0B+UpsDoqFkOfLvO5HN8AMN5lOJUU2+2IMD0ne0QOtCcq0k7OANe1VGToag7qaBRXeiFFAJjOyBENsV20Jqcgj2FQHgvyJWYvAQfPAJuvAv7198ADm4DMHJBKAmuag5ypemPpGNiVWk2pDcCDDDQCPTU7EOgmjrxMRgA8dgBYmwJOXwBuWgH87m3gz26OLgDy6q9G9RSLvIAymFZUGsaCjJzE7qB1+vvngXRGQebG5QB/P30eaF2iQBHllk8wxdDfGq/eYVLLQJBfEOQNOpk3/Bg86hbA8iAZwt2/a78asX8zsKRJ/fzYQeDttFIHUbcJqi0JnM4FaOX9g2Sw7lgHTgPTs0DHRxTjT5wDtqzTfTr8/aoJArfTwX055P1519q6apGV4v8/XlU6nzv/vo8CvzwK3L0W2LS6Wquo/DzVAoFrMiivyzVSvpUnycIMVAUU///2kgIC9b+pDqq5jirMVXEX0RUAfMkwgoDrMoFQBUbUcoqxVFJsrdQCtAAQZhBUijChGlfiwdSSypx81gaAAQLLA6OhIlaNF2MGqriMm1cFqJoEtlai0lobAIzqZbPYF7RrV2N+BT79L99c8Eh+dzI474RGYSqB9qDyBuaLawMgnZE8Exjau24C56TPAZ8+vsD0594GPrHW50DWjwVuD2gBwDgm/q1AX6VOB/v5YeDWNerlXnsf+MKGYF/ULQvrdTZXAFQioeN1kVHqTxuAgSo2xiWCdk+DVgWuAEjPyt31XqARJYAZa92ZSqq7CMttjgCoh1r9cgkU1ueDuq7PEQDTs/JY0Nm8sBI0gusKxCCMZDIogsyqzJIDiA3YAqCx+yvDs4BHLVsKBFoPEPDLNYbToUCZUsASAFHY/SfTCxWtq5qBK4ziEB2a1VmfsqTAIgBUupLXL/Evshj0NNCaBM7NAr89sTDSp64HOj/id+ToP1eOR7AYAD4qg6tBwufeARhabV8BfPJaFWxZEgeSMWDTmstaApD8vuMCRQAI47Gs37wFrG4GrksBR882mG212VhW3rpEtPvZiL6OhvmZyOszFPm7D6qqX+76L6xfGIH/l7mkVMGNKy773Z8njIyh1081cfHBkJAEfgqZv2kV8Cmj4HP/H4ADZ1RBqNmYbfvEtV7hVX/9/X7HYR4AYUr6PPmmYjSZ/7kblRT47yOK8Uyu8DxA6xXqHADVw2XsARQhuSWBFV7rBeYBoHsyuNJ7582zwBNHgNVLgb+8TTH/8YOq/o+7ffPVCwynpAiC+W+cUfOcnwOuvlIdO4ti86MG5gGQdrkFtFoE2fUykJ4F+u4AknHgx6+pnf/ZduCW1QD9f3oDPA/A5tcFpA3x8ingxXcVuPJ61Dgt27MRuKG1Wm8c6DyevYE8AMJk/f/nq8BNK5VeN10/k8mvnwZ+ZRw1pfjnYdCOq1VZuJd2/Bzwv0cXGL9hpdr1PGNIgMUF8Oe3AquWehk1FH0nUknR6WUleQCENe37vZeUFOi/AyhUDbQL/JwE5q4ngI4YNxBxl7NiZ5kBoCePAb9/R4mBq64EejcGX9DhhTl++sYTaPdya7sCgPoySM1LvujzM8Dzd3cuMNy08ikZeBqYqoE7njv1t28p3c3f/+hapSLs2jszAI1Luo40JMn4j64s7v3PzympQNeSgLtnLfDxiHkYXkvGFAA0r2Yjsf3sPF0km/r/H+9aEP8P8ORPEhh5ecErIPNpGLJxF7/3oWLcfesV80rboQ+UyDf7f3HTYuPx+XeAX00Ct68BPn0D8O8TasyvbgZakrpvEIp+nuyAPACmM/KsW7k3b+N4/ABA5lSq8cIH0/o39f9f3KoYQYab0uDnR4BjZ4Gv3LYAgB+9qiQB+xe2X0+q4kw22hL8V9roTXz3BTXP1z6mVAJzDfxHqUIDNELN02f9hI7/T6v7zSklbml8MfFCXzzoRp1Pv57n/Wj5M/RrJnn2/QG4rlVJIPM4+DfvWVjB9/YpFfFJIzBEZv7iMHAirUT+59qBO2zcu7zuf1ftfrOKtxAUVElBF3cGTTtzPBaNtiaFhRy0nlHoGIDc/a+fUlez0Dq/ZY26kiXoRoCR0G5WvRkoMnf0/xwFXnwPiAFINgE3LAOmLwKnjPEoKejfW7Xj0+q0Mef92seLVQNLvF95H/iTm4CNJfZC0O8e5HheAkJCNwBkHsv++zuDXKq/sSgd/uu1xf47gfnWOR5mVeNe1Qx8adOClW8l+n8woQzDL9682Cik7fDTg8D1KaD3Zn9rrdRTlHA0mmmvLJJOHopERFg8AK+EIgiomo6dAy7OKdXwNx3AiRngRy8DOQl0XgN8Zp19tJDMJZMLRX/pOh7eqwhMNRCmZtpkVreiePEECADLj0SH6WWd1kIgPP6G2sVty4Av36pE/+43gDMX1K6+b8NiEJhGHqXEV263Bwm9AY731Q6gJVF7qnDnT7ynbCS6zDx8QpXJgNi8JPBwmljouoC1f3X7FdDHH30DmMsBG1YosU+m0Vg8exG4pkV5B2begCFg5htIMCf7gDOadsCXbgGusbEjqkkbMp+iv7QR5POG+eUGADL/rWmgSQBzEui4CvjT9cBUBnjikPqbCYJzF4EfGBdNMW5g5xmYBKY3cOgMsGEVcClXTVY7z+WkArxUCIkoFIA6keLXx4DXTiuDb2ubihdkcsCnDZ+fDPzZIRX+pYvJRBPF6GfbVPRQt124FE4AULJZBOe0YwFUAZG9mv2Fd4G9J4tdOEb0fnNCVQxtuQHoug4gCH5yADgxrdjtZPRZeQqvnFISgEmiMDV6ZjbueP0DgBm9PYes9Tizhr84mr98Ol9Qwps66OszZOyF+WS2GSQKiw2gCcD6BkBMABT9DNLY6XFKgicnVWCIuQR6BMwb0Ojz0hhipg3x5dsilR6ubwA0xwGCgDvaLsJHJtPPP/yBKvQg862SQG5gYByArZI5ELc1+Pi7PgCmM5JfuArNRZBuL0uGMkdg5dsXPktwcPfS4PMq9s1xzDAxS9KZlYxQ0wdAlOIAFOPU5Wxm1s6KKfTzqbvLYT7HNWMANCZZixihVp8AYHSPlrxdWpcMMiN8/Pmua1QW0E+LajbQeNf6A4BZ1MGw5z98bDFLybAnDqvYPptOkMcJGBGuB+BraReFRCYU/OPXgVMfWjOW+p4Gn1nu5RbedZMIHOe7L6pevJa+EqlvtzWU9XdPoeCQ1AM6vbCp+0t3P3c96wJZzMHG6l4WdJR7VuCR54ELc6q4xEu0sCymBfiwp2ygbj1AgOvzPBQjcU8dL9bpNPQops1dT7sgCGYxScSxGURa2+rubXh+mWo84KkeICu7IcFbQEPb6Pqx6mdpXBV9HvlgoRiktLS7nJcw9X4ipuIM9CJWXqHSxVFqXkrDRZgOhdgROZsDvv8ScMnMWgiV9uWOXxfACR6qErp8BJpVY7KF2UW3UrUwgEQC51qTQrtiU1UFz8pJIRDqj67MzAGPva5EPsO7zPkHwXyO98NX1IeomE4myFgnQHAxysiQMkPBLIZlsWoEmrYLyHfxdC6g1i/PxfJkD9O/bOWIf/OaGep7Nur8eEwBoNCFpHQw6wb/9s7yDcwq0FDbBVwAQAQ8gULCUVSPn1y4J4BlX+uWqfIv85iXFaHN4g7uaJ4INr84wtLzrAReeFs99Ve3Fx8O/ZfnVd3hXdeqbxOFuXnxAOYBENaLoZwITWnA3ct6APN0L/tTbFOEXxFXP3OnT11UI5HxhY2HPnjYhCqFpeY8+8BWGGnk89/fB9AOYWPRCXMLYW1eDMB5APCHqBaGEAisDWCI+PDZ4ttDSplEYKxfody79uUKKOapI55J4Glg2gIsJqF6IYAIMvZh/2MGQG5bA3ymLXgI0IATAj3ml0G8VmxLieOtS4SnlYXufoByydoUUwdHeUhkdk6dLqbzwCNndO8KG5nO8DGZblYHs2bwJweLgWQGmFgbyDgBG08OXx+AB1K0IAv/3ctHPP1cExO6G0LKBYDu82Q+q4a52ynSWTlUGEFkKpht+ZJiu4L/T8/g8+1Ac6JY/ejObdcvlRSWRWc8vSUlRt3S9mXdEKJzRrDcFwzL86ynZ7qYzW+tAA3Knx5Qhaa0GcotrHQ706f16T6J0ZYk+r3cE1SEuHRGTgDYHBZGVWIdhcz3WhlcuJ7CdDGDUn/crozJcprbDtYCAeDpY5PFAIiYO+iV2Nz15udcyk0Xc+7CqiPeKPLAxvJAoPO5WOOr7KMugTttEBQBoJ7VQCHzGS9wqiX0AiyCwKxBqBYIjPA9v9LuJK0n4gn0ul0XE5m7gr0wpbSveYqmnDF0nw3qWhm3gI4OCHQkyiIA6NwXoEuMsPSjH8/IX2ausiuiIcjawXJtAXOVgYBAYjKVRKedYWj3vYDQJ4cqy8rwjO4GAq502uWGdyfjsvHFkPDw2n4lAoOphHjQaalOIHACkSUAqF/SWUy6BR6iQLt6WaMERlqTot8rCBheTiTQYWcM2h539BKCrBcih/09dEBAvuWAYW5eMh8x9JnXyBu5hS359xQYa4ljp+N51ygUioSdaUGvjyBIJbDdLdpHKV7YxyqxxNyBIwDq0SMImiE1Gk870GOuz+oEGN1E1xPvUTo6ViNm1GpaTyCwC/O7AoChRwnsq9VbNuZ1pIA2CCy9BIk9rgDg9OmMHAbw9QYzQkkBLRAYkcNRAMoIBPYLoE8LAAYI6j5TGEr26i3KUyFo4ZDaANBMReott9ErUAq41RI4TaYNAA4ShWNkgVI2QoPZVRO5vYItAIbGZYcQWJZZiv2DncIoh8x/XCLSN4u6ESSqfw8UAEPjeaOvj1+kBLCuqQmd37hb8Gfz+0Juueio0jGS6/ZTDGq+6CIJMLhPLk+cx9lYDO1k+kPPyBEhMDnQJQbNh1g4ks1iopErqDhe9scT6JnLYBgC91vN5hbrd1vhIgB851nZlsvhWLYZKyj6DWmAgS6xrXCwhlHoRtry/s4a/1QSHWY4l6FcKbCtcNOxT0ygpyUp6KH5apY2gLHrt1AFCIHOWAwdpgoonKURKvZFc9eHuKtjQHcpY/Mle3NoQw7dsgkTfr4VXDq5rRH48F7Zk8uhrakJo1bMNwdqZA1d+empgx3zPQ3iobMnN9Bu3AYIPFDcoWu1mc+lBAIADsQDpshhpGEY+gNDLZgfCADoNSQvqPLkTSuxfuNq7IwJhODTCv4YUYunsjkc3f8+Bo7M4v8KYy5cixmPKVxXaWymnDWXLQGGxiVjAmaCoZy1XPbPSon9O+4VHSYhaIdJid2lhJESUzvu1f80nBNhywYA3cY5oOhI8uqlaLk+hfWXPUc1CHAijSOnL2CGXePAZKHBzd0PgEG50jZR6pZrTGXZpWwA+J248Vw4KNAAQDj4ULNVBA6Abz8ru8Ul9SVyITCVbUY/DRsjxLxLSmhdYSYEHh3oEiMch2rm0iXssqHSxI57xfZKU/ChZ+QjAOb1c6XnM+g3T4NKzRc4AIbGZR8TSeaCYzH0Ua8ZIeY8QzXbSBEAcvlSZyvwBKYPndb10LjkBQ1a4NV8P51u8zTQ6eynT+AA8LOIxjO1o0ADALWjfShmjhwACgNPoaCgsnWOO+VLwrJOq3VEDgCGLrbMjdeK0EEGZqr9DpEDgFXgqdpEK50vlsPUQJf/nHwt1///89bqDjCPWPAAAAAASUVORK5CYII=\");\n\t\tbackground-position: center;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/components/qiun-loading/loading1.vue",
    "content": "<template>\n\t <view class=\"container loading1\">\n\t\t<view class=\"shape shape1\"></view>\n\t\t<view class=\"shape shape2\"></view>\n\t\t<view class=\"shape shape3\"></view>\n\t\t<view class=\"shape shape4\"></view>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tname: 'loading1',\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 scoped=\"true\">\n.container {\n  width: 30px;\n  height: 30px;\n  position: relative;\n}\n.container.loading1 {\n  -webkit-transform: rotate(45deg);\n          transform: rotate(45deg);\n}\n\n.container .shape {\n  position: absolute;\n  width: 10px;\n  height: 10px;\n  border-radius: 1px;\n}\n.container .shape.shape1 {\n  left: 0;\n  background-color: #1890FF;\n}\n.container .shape.shape2 {\n  right: 0;\n  background-color: #91CB74;\n}\n.container .shape.shape3 {\n  bottom: 0;\n  background-color: #FAC858;\n}\n.container .shape.shape4 {\n  bottom: 0;\n  right: 0;\n  background-color: #EE6666;\n}\n\n.loading1 .shape1 {\n  -webkit-animation: animation1shape1 0.5s ease 0s infinite alternate;\n          animation: animation1shape1 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation1shape1 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(16px, 16px);\n            transform: translate(16px, 16px);\n  }\n}\n\n@keyframes animation1shape1 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(16px, 16px);\n            transform: translate(16px, 16px);\n  }\n}\n.loading1 .shape2 {\n  -webkit-animation: animation1shape2 0.5s ease 0s infinite alternate;\n          animation: animation1shape2 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation1shape2 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-16px, 16px);\n            transform: translate(-16px, 16px);\n  }\n}\n\n@keyframes animation1shape2 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-16px, 16px);\n            transform: translate(-16px, 16px);\n  }\n}\n.loading1 .shape3 {\n  -webkit-animation: animation1shape3 0.5s ease 0s infinite alternate;\n          animation: animation1shape3 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation1shape3 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(16px, -16px);\n            transform: translate(16px, -16px);\n  }\n}\n\n@keyframes animation1shape3 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(16px, -16px);\n            transform: translate(16px, -16px);\n  }\n}\n.loading1 .shape4 {\n  -webkit-animation: animation1shape4 0.5s ease 0s infinite alternate;\n          animation: animation1shape4 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation1shape4 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-16px, -16px);\n            transform: translate(-16px, -16px);\n  }\n}\n\n@keyframes animation1shape4 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-16px, -16px);\n            transform: translate(-16px, -16px);\n  }\n}\n\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/components/qiun-loading/loading2.vue",
    "content": "<template>\n\t <view class=\"container loading2\">\n\t\t<view class=\"shape shape1\"></view>\n\t\t<view class=\"shape shape2\"></view>\n\t\t<view class=\"shape shape3\"></view>\n\t\t<view class=\"shape shape4\"></view>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tname: 'loading2',\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 scoped=\"true\">\n.container {\n  width: 30px;\n  height: 30px;\n  position: relative;\n}\n\n.container.loading2 {\n  -webkit-transform: rotate(10deg);\n          transform: rotate(10deg);\n}\n.container.loading2 .shape {\n  border-radius: 5px;\n}\n.container.loading2{\n  -webkit-animation: rotation 1s infinite;\n          animation: rotation 1s infinite;\n}\n\n.container .shape {\n  position: absolute;\n  width: 10px;\n  height: 10px;\n  border-radius: 1px;\n}\n.container .shape.shape1 {\n  left: 0;\n  background-color: #1890FF;\n}\n.container .shape.shape2 {\n  right: 0;\n  background-color: #91CB74;\n}\n.container .shape.shape3 {\n  bottom: 0;\n  background-color: #FAC858;\n}\n.container .shape.shape4 {\n  bottom: 0;\n  right: 0;\n  background-color: #EE6666;\n}\n\n\n.loading2 .shape1 {\n  -webkit-animation: animation2shape1 0.5s ease 0s infinite alternate;\n          animation: animation2shape1 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation2shape1 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(20px, 20px);\n            transform: translate(20px, 20px);\n  }\n}\n\n@keyframes animation2shape1 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(20px, 20px);\n            transform: translate(20px, 20px);\n  }\n}\n.loading2 .shape2 {\n  -webkit-animation: animation2shape2 0.5s ease 0s infinite alternate;\n          animation: animation2shape2 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation2shape2 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-20px, 20px);\n            transform: translate(-20px, 20px);\n  }\n}\n\n@keyframes animation2shape2 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-20px, 20px);\n            transform: translate(-20px, 20px);\n  }\n}\n.loading2 .shape3 {\n  -webkit-animation: animation2shape3 0.5s ease 0s infinite alternate;\n          animation: animation2shape3 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation2shape3 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(20px, -20px);\n            transform: translate(20px, -20px);\n  }\n}\n\n@keyframes animation2shape3 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(20px, -20px);\n            transform: translate(20px, -20px);\n  }\n}\n.loading2 .shape4 {\n  -webkit-animation: animation2shape4 0.5s ease 0s infinite alternate;\n          animation: animation2shape4 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation2shape4 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-20px, -20px);\n            transform: translate(-20px, -20px);\n  }\n}\n\n@keyframes animation2shape4 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-20px, -20px);\n            transform: translate(-20px, -20px);\n  }\n}\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/components/qiun-loading/loading3.vue",
    "content": "<template>\n\t <view class=\"container loading3\">\n\t\t<view class=\"shape shape1\"></view>\n\t\t<view class=\"shape shape2\"></view>\n\t\t<view class=\"shape shape3\"></view>\n\t\t<view class=\"shape shape4\"></view>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tname: 'loading3',\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 scoped=\"true\">\n.container {\n  width: 30px;\n  height: 30px;\n  position: relative;\n}\n\n .container.loading3 {\n  -webkit-animation: rotation 1s infinite;\n          animation: rotation 1s infinite;\n}\n.container.loading3 .shape1 {\n  border-top-left-radius: 10px;\n}\n.container.loading3 .shape2 {\n  border-top-right-radius: 10px;\n}\n.container.loading3 .shape3 {\n  border-bottom-left-radius: 10px;\n}\n.container.loading3 .shape4 {\n  border-bottom-right-radius: 10px;\n}\n\n.container .shape {\n  position: absolute;\n  width: 10px;\n  height: 10px;\n  border-radius: 1px;\n}\n.container .shape.shape1 {\n  left: 0;\n  background-color: #1890FF;\n}\n.container .shape.shape2 {\n  right: 0;\n  background-color: #91CB74;\n}\n.container .shape.shape3 {\n  bottom: 0;\n  background-color: #FAC858;\n}\n.container .shape.shape4 {\n  bottom: 0;\n  right: 0;\n  background-color: #EE6666;\n}\n\n.loading3 .shape1 {\n  -webkit-animation: animation3shape1 0.5s ease 0s infinite alternate;\n          animation: animation3shape1 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation3shape1 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(5px, 5px);\n            transform: translate(5px, 5px);\n  }\n}\n\n@keyframes animation3shape1 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(5px, 5px);\n            transform: translate(5px, 5px);\n  }\n}\n.loading3 .shape2 {\n  -webkit-animation: animation3shape2 0.5s ease 0s infinite alternate;\n          animation: animation3shape2 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation3shape2 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-5px, 5px);\n            transform: translate(-5px, 5px);\n  }\n}\n\n@keyframes animation3shape2 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-5px, 5px);\n            transform: translate(-5px, 5px);\n  }\n}\n.loading3 .shape3 {\n  -webkit-animation: animation3shape3 0.5s ease 0s infinite alternate;\n          animation: animation3shape3 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation3shape3 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(5px, -5px);\n            transform: translate(5px, -5px);\n  }\n}\n\n@keyframes animation3shape3 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(5px, -5px);\n            transform: translate(5px, -5px);\n  }\n}\n.loading3 .shape4 {\n  -webkit-animation: animation3shape4 0.5s ease 0s infinite alternate;\n          animation: animation3shape4 0.5s ease 0s infinite alternate;\n}\n\n@-webkit-keyframes animation3shape4 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-5px, -5px);\n            transform: translate(-5px, -5px);\n  }\n}\n\n@keyframes animation3shape4 {\n  from {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  to {\n    -webkit-transform: translate(-5px, -5px);\n            transform: translate(-5px, -5px);\n  }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/components/qiun-loading/loading4.vue",
    "content": "<template>\n\t <view class=\"container loading5\">\n\t\t<view class=\"shape shape1\"></view>\n\t\t<view class=\"shape shape2\"></view>\n\t\t<view class=\"shape shape3\"></view>\n\t\t<view class=\"shape shape4\"></view>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tname: 'loading5',\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 scoped=\"true\">\n.container {\n  width: 30px;\n  height: 30px;\n  position: relative;\n}\n\n.container.loading5 .shape {\n  width: 15px;\n  height: 15px;\n}\n\n.container .shape {\n  position: absolute;\n  width: 10px;\n  height: 10px;\n  border-radius: 1px;\n}\n.container .shape.shape1 {\n  left: 0;\n  background-color: #1890FF;\n}\n.container .shape.shape2 {\n  right: 0;\n  background-color: #91CB74;\n}\n.container .shape.shape3 {\n  bottom: 0;\n  background-color: #FAC858;\n}\n.container .shape.shape4 {\n  bottom: 0;\n  right: 0;\n  background-color: #EE6666;\n}\n\n.loading5 .shape1 {\n  animation: animation5shape1 2s ease 0s infinite reverse;\n}\n\n@-webkit-keyframes animation5shape1 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(0, 15px);\n            transform: translate(0, 15px);\n  }\n  50% {\n    -webkit-transform: translate(15px, 15px);\n            transform: translate(15px, 15px);\n  }\n  75% {\n    -webkit-transform: translate(15px, 0);\n            transform: translate(15px, 0);\n  }\n}\n\n@keyframes animation5shape1 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(0, 15px);\n            transform: translate(0, 15px);\n  }\n  50% {\n    -webkit-transform: translate(15px, 15px);\n            transform: translate(15px, 15px);\n  }\n  75% {\n    -webkit-transform: translate(15px, 0);\n            transform: translate(15px, 0);\n  }\n}\n.loading5 .shape2 {\n  animation: animation5shape2 2s ease 0s infinite reverse;\n}\n\n@-webkit-keyframes animation5shape2 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(-15px, 0);\n            transform: translate(-15px, 0);\n  }\n  50% {\n    -webkit-transform: translate(-15px, 15px);\n            transform: translate(-15px, 15px);\n  }\n  75% {\n    -webkit-transform: translate(0, 15px);\n            transform: translate(0, 15px);\n  }\n}\n\n@keyframes animation5shape2 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(-15px, 0);\n            transform: translate(-15px, 0);\n  }\n  50% {\n    -webkit-transform: translate(-15px, 15px);\n            transform: translate(-15px, 15px);\n  }\n  75% {\n    -webkit-transform: translate(0, 15px);\n            transform: translate(0, 15px);\n  }\n}\n.loading5 .shape3 {\n  animation: animation5shape3 2s ease 0s infinite reverse;\n}\n\n@-webkit-keyframes animation5shape3 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(15px, 0);\n            transform: translate(15px, 0);\n  }\n  50% {\n    -webkit-transform: translate(15px, -15px);\n            transform: translate(15px, -15px);\n  }\n  75% {\n    -webkit-transform: translate(0, -15px);\n            transform: translate(0, -15px);\n  }\n}\n\n@keyframes animation5shape3 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(15px, 0);\n            transform: translate(15px, 0);\n  }\n  50% {\n    -webkit-transform: translate(15px, -15px);\n            transform: translate(15px, -15px);\n  }\n  75% {\n    -webkit-transform: translate(0, -15px);\n            transform: translate(0, -15px);\n  }\n}\n.loading5 .shape4 {\n  animation: animation5shape4 2s ease 0s infinite reverse;\n}\n\n@-webkit-keyframes animation5shape4 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(0, -15px);\n            transform: translate(0, -15px);\n  }\n  50% {\n    -webkit-transform: translate(-15px, -15px);\n            transform: translate(-15px, -15px);\n  }\n  75% {\n    -webkit-transform: translate(-15px, 0);\n            transform: translate(-15px, 0);\n  }\n}\n\n@keyframes animation5shape4 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(0, -15px);\n            transform: translate(0, -15px);\n  }\n  50% {\n    -webkit-transform: translate(-15px, -15px);\n            transform: translate(-15px, -15px);\n  }\n  75% {\n    -webkit-transform: translate(-15px, 0);\n            transform: translate(-15px, 0);\n  }\n}\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/components/qiun-loading/loading5.vue",
    "content": "<template>\n\t <view class=\"container loading6\">\n\t\t<view class=\"shape shape1\"></view>\n\t\t<view class=\"shape shape2\"></view>\n\t\t<view class=\"shape shape3\"></view>\n\t\t<view class=\"shape shape4\"></view>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tname: 'loading6',\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t\n\t\t\t};\n\t\t}\n\t}\n</script>\n<style scoped=\"true\">\n.container {\n  width: 30px;\n  height: 30px;\n  position: relative;\n}\n\n.container.loading6 {\n  -webkit-animation: rotation 1s infinite;\n          animation: rotation 1s infinite;\n}\n.container.loading6 .shape {\n  width: 12px;\n  height: 12px;\n  border-radius: 2px;\n}\n.container .shape {\n  position: absolute;\n  width: 10px;\n  height: 10px;\n  border-radius: 1px;\n}\n.container .shape.shape1 {\n  left: 0;\n  background-color: #1890FF;\n}\n.container .shape.shape2 {\n  right: 0;\n  background-color: #91CB74;\n}\n.container .shape.shape3 {\n  bottom: 0;\n  background-color: #FAC858;\n}\n.container .shape.shape4 {\n  bottom: 0;\n  right: 0;\n  background-color: #EE6666;\n}\n\n\n.loading6 .shape1 {\n  -webkit-animation: animation6shape1 2s linear 0s infinite normal;\n          animation: animation6shape1 2s linear 0s infinite normal;\n}\n\n@-webkit-keyframes animation6shape1 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(0, 18px);\n            transform: translate(0, 18px);\n  }\n  50% {\n    -webkit-transform: translate(18px, 18px);\n            transform: translate(18px, 18px);\n  }\n  75% {\n    -webkit-transform: translate(18px, 0);\n            transform: translate(18px, 0);\n  }\n}\n\n@keyframes animation6shape1 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(0, 18px);\n            transform: translate(0, 18px);\n  }\n  50% {\n    -webkit-transform: translate(18px, 18px);\n            transform: translate(18px, 18px);\n  }\n  75% {\n    -webkit-transform: translate(18px, 0);\n            transform: translate(18px, 0);\n  }\n}\n.loading6 .shape2 {\n  -webkit-animation: animation6shape2 2s linear 0s infinite normal;\n          animation: animation6shape2 2s linear 0s infinite normal;\n}\n\n@-webkit-keyframes animation6shape2 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(-18px, 0);\n            transform: translate(-18px, 0);\n  }\n  50% {\n    -webkit-transform: translate(-18px, 18px);\n            transform: translate(-18px, 18px);\n  }\n  75% {\n    -webkit-transform: translate(0, 18px);\n            transform: translate(0, 18px);\n  }\n}\n\n@keyframes animation6shape2 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(-18px, 0);\n            transform: translate(-18px, 0);\n  }\n  50% {\n    -webkit-transform: translate(-18px, 18px);\n            transform: translate(-18px, 18px);\n  }\n  75% {\n    -webkit-transform: translate(0, 18px);\n            transform: translate(0, 18px);\n  }\n}\n.loading6 .shape3 {\n  -webkit-animation: animation6shape3 2s linear 0s infinite normal;\n          animation: animation6shape3 2s linear 0s infinite normal;\n}\n\n@-webkit-keyframes animation6shape3 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(18px, 0);\n            transform: translate(18px, 0);\n  }\n  50% {\n    -webkit-transform: translate(18px, -18px);\n            transform: translate(18px, -18px);\n  }\n  75% {\n    -webkit-transform: translate(0, -18px);\n            transform: translate(0, -18px);\n  }\n}\n\n@keyframes animation6shape3 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(18px, 0);\n            transform: translate(18px, 0);\n  }\n  50% {\n    -webkit-transform: translate(18px, -18px);\n            transform: translate(18px, -18px);\n  }\n  75% {\n    -webkit-transform: translate(0, -18px);\n            transform: translate(0, -18px);\n  }\n}\n.loading6 .shape4 {\n  -webkit-animation: animation6shape4 2s linear 0s infinite normal;\n          animation: animation6shape4 2s linear 0s infinite normal;\n}\n\n@-webkit-keyframes animation6shape4 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(0, -18px);\n            transform: translate(0, -18px);\n  }\n  50% {\n    -webkit-transform: translate(-18px, -18px);\n            transform: translate(-18px, -18px);\n  }\n  75% {\n    -webkit-transform: translate(-18px, 0);\n            transform: translate(-18px, 0);\n  }\n}\n\n@keyframes animation6shape4 {\n  0% {\n    -webkit-transform: translate(0, 0);\n            transform: translate(0, 0);\n  }\n  25% {\n    -webkit-transform: translate(0, -18px);\n            transform: translate(0, -18px);\n  }\n  50% {\n    -webkit-transform: translate(-18px, -18px);\n            transform: translate(-18px, -18px);\n  }\n  75% {\n    -webkit-transform: translate(-18px, 0);\n            transform: translate(-18px, 0);\n  }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/components/qiun-loading/qiun-loading.vue",
    "content": "<template>\n\t<view>\n\t <Loading1 v-if=\"loadingType==1\"/>\n\t <Loading2 v-if=\"loadingType==2\"/>\n\t <Loading3 v-if=\"loadingType==3\"/>\n\t <Loading4 v-if=\"loadingType==4\"/>\n\t <Loading5 v-if=\"loadingType==5\"/>\n\t</view>\n</template>\n\n<script>\n\timport Loading1 from \"./loading1.vue\";\n\timport Loading2 from \"./loading2.vue\";\n\timport Loading3 from \"./loading3.vue\";\n\timport Loading4 from \"./loading4.vue\";\n\timport Loading5 from \"./loading5.vue\";\n\texport default {\n\t\tcomponents:{Loading1,Loading2,Loading3,Loading4,Loading5},\n\t\tname: 'qiun-loading',\n\t\tprops: {\n\t\t\tloadingType: {\n\t\t\t\ttype: Number,\n\t\t\t\tdefault: 2\n\t\t\t},\n\t\t},\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>\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/js_sdk/u-charts/config-echarts.js",
    "content": "/*\n * uCharts®\n * 高性能跨平台图表库，支持H5、APP、小程序（微信/支付宝/百度/头条/QQ/360）、Vue、Taro等支持canvas的框架平台\n * Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved.\n * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n * 复制使用请保留本段注释，感谢支持开源！\n * \n * uCharts®官方网站\n * https://www.uCharts.cn\n * \n * 开源地址:\n * https://gitee.com/uCharts/uCharts\n * \n * uni-app插件市场地址：\n * http://ext.dcloud.net.cn/plugin?id=271\n * \n */\n\n// 通用配置项\n\n// 主题颜色配置：如每个图表类型需要不同主题，请在对应图表类型上更改color属性\nconst color = ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'];\n\nconst cfe = {\n  //demotype为自定义图表类型\n\t\"type\": [\"pie\", \"ring\", \"rose\", \"funnel\", \"line\", \"column\", \"area\", \"radar\", \"gauge\",\"candle\",\"demotype\"],\n  //增加自定义图表类型，如果需要categories，请在这里加入您的图表类型例如最后的\"demotype\"\n\t\"categories\": [\"line\", \"column\", \"area\", \"radar\", \"gauge\", \"candle\",\"demotype\"],\n  //instance为实例变量承载属性，option为eopts承载属性，不要删除\n\t\"instance\": {},\n\t\"option\": {},\n  //下面是自定义format配置，因除H5端外的其他端无法通过props传递函数，只能通过此属性对应下标的方式来替换\n  \"formatter\":{\n    \"tooltipDemo1\":function(res){\n      let result = ''\n      for (let i in res) {\n      \tif (i == 0) {\n      \t\tresult += res[i].axisValueLabel + '年销售额'\n      \t}\n      \tlet value = '--'\n      \tif (res[i].data !== null) {\n      \t\tvalue = res[i].data\n      \t}\n      \t// #ifdef H5\n      \tresult += '\\n' + res[i].seriesName + '：' + value + ' 万元'\n      \t// #endif\n      \t\n      \t// #ifdef APP-PLUS\n      \tresult += '<br/>' + res[i].marker + res[i].seriesName + '：' + value + ' 万元'\n      \t// #endif\n      }\n      return result;\n    },\n    legendFormat:function(name){\n      return \"自定义图例+\"+name;\n    },\n    yAxisFormatDemo:function (value, index) {\n      return value + '元';\n    },\n    seriesFormatDemo:function(res){\n      return res.name + '年' + res.value + '元';\n    }\n  },\n  //这里演示了自定义您的图表类型的option，可以随意命名，之后在组件上 type=\"demotype\" 后，组件会调用这个花括号里的option，如果组件上还存在eopts参数，会将demotype与eopts中option合并后渲染图表。\n  \"demotype\":{\n    \"color\": color,\n    //在这里填写echarts的option即可\n    \n  },\n  //下面是自定义配置，请添加项目所需的通用配置\n\t\"column\": {\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"text\": ''\n\t\t},\n\t\t\"tooltip\": {\n\t\t\t\"trigger\": 'axis'\n\t\t},\n\t\t\"grid\": {\n\t\t\t\"top\": 30,\n\t\t\t\"bottom\": 50,\n\t\t\t\"right\": 15,\n\t\t\t\"left\": 40\n\t\t},\n\t\t\"legend\": {\n\t\t\t\"bottom\": 'left',\n\t\t},\n\t\t\"toolbox\": {\n\t\t\t\"show\": false,\n\t\t},\n\t\t\"xAxis\": {\n\t\t\t\"type\": 'category',\n\t\t\t\"axisLabel\": {\n\t\t\t\t\"color\": '#666666'\n\t\t\t},\n\t\t\t\"axisLine\": {\n\t\t\t\t\"lineStyle\": {\n\t\t\t\t\t\"color\": '#CCCCCC'\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"boundaryGap\": true,\n\t\t\t\"data\": []\n\t\t},\n\t\t\"yAxis\": {\n\t\t\t\"type\": 'value',\n\t\t\t\"axisTick\": {\n\t\t\t\t\"show\": false,\n\t\t\t},\n\t\t\t\"axisLabel\": {\n\t\t\t\t\"color\": '#666666'\n\t\t\t},\n\t\t\t\"axisLine\": {\n\t\t\t\t\"lineStyle\": {\n\t\t\t\t\t\"color\": '#CCCCCC'\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '',\n\t\t\t\"type\": 'bar',\n\t\t\t\"data\": [],\n\t\t\t\"barwidth\": 20,\n\t\t\t\"label\": {\n\t\t\t\t\"show\": true,\n        \"color\": \"#666666\",\n\t\t\t\t\"position\": 'top',\n\t\t\t},\n\t\t},\n\t},\n\t\"line\": {\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"text\": ''\n\t\t},\n\t\t\"tooltip\": {\n\t\t\t\"trigger\": 'axis'\n\t\t},\n\t\t\"grid\": {\n\t\t\t\"top\": 30,\n\t\t\t\"bottom\": 50,\n\t\t\t\"right\": 15,\n\t\t\t\"left\": 40\n\t\t},\n\t\t\"legend\": {\n\t\t\t\"bottom\": 'left',\n\t\t},\n\t\t\"toolbox\": {\n\t\t\t\"show\": false,\n\t\t},\n\t\t\"xAxis\": {\n\t\t\t\"type\": 'category',\n\t\t\t\"axisLabel\": {\n\t\t\t\t\"color\": '#666666'\n\t\t\t},\n\t\t\t\"axisLine\": {\n\t\t\t\t\"lineStyle\": {\n\t\t\t\t\t\"color\": '#CCCCCC'\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"boundaryGap\": true,\n\t\t\t\"data\": []\n\t\t},\n\t\t\"yAxis\": {\n\t\t\t\"type\": 'value',\n\t\t\t\"axisTick\": {\n\t\t\t\t\"show\": false,\n\t\t\t},\n\t\t\t\"axisLabel\": {\n\t\t\t\t\"color\": '#666666'\n\t\t\t},\n\t\t\t\"axisLine\": {\n\t\t\t\t\"lineStyle\": {\n\t\t\t\t\t\"color\": '#CCCCCC'\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '',\n\t\t\t\"type\": 'line',\n\t\t\t\"data\": [],\n\t\t\t\"barwidth\": 20,\n\t\t\t\"label\": {\n\t\t\t\t\"show\": true,\n        \"color\": \"#666666\",\n\t\t\t\t\"position\": 'top',\n\t\t\t},\n\t\t},\n\t},\n\t\"area\": {\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"text\": ''\n\t\t},\n\t\t\"tooltip\": {\n\t\t\t\"trigger\": 'axis'\n\t\t},\n\t\t\"grid\": {\n\t\t\t\"top\": 30,\n\t\t\t\"bottom\": 50,\n\t\t\t\"right\": 15,\n\t\t\t\"left\": 40\n\t\t},\n\t\t\"legend\": {\n\t\t\t\"bottom\": 'left',\n\t\t},\n\t\t\"toolbox\": {\n\t\t\t\"show\": false,\n\t\t},\n\t\t\"xAxis\": {\n\t\t\t\"type\": 'category',\n\t\t\t\"axisLabel\": {\n\t\t\t\t\"color\": '#666666'\n\t\t\t},\n\t\t\t\"axisLine\": {\n\t\t\t\t\"lineStyle\": {\n\t\t\t\t\t\"color\": '#CCCCCC'\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"boundaryGap\": true,\n\t\t\t\"data\": []\n\t\t},\n\t\t\"yAxis\": {\n\t\t\t\"type\": 'value',\n\t\t\t\"axisTick\": {\n\t\t\t\t\"show\": false,\n\t\t\t},\n\t\t\t\"axisLabel\": {\n\t\t\t\t\"color\": '#666666'\n\t\t\t},\n\t\t\t\"axisLine\": {\n\t\t\t\t\"lineStyle\": {\n\t\t\t\t\t\"color\": '#CCCCCC'\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '',\n\t\t\t\"type\": 'line',\n\t\t\t\"data\": [],\n\t\t\t\"areaStyle\": {},\n\t\t\t\"label\": {\n\t\t\t\t\"show\": true,\n        \"color\": \"#666666\",\n\t\t\t\t\"position\": 'top',\n\t\t\t},\n\t\t},\n\t},\n\t\"pie\": {\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"text\": ''\n\t\t},\n\t\t\"tooltip\": {\n\t\t\t\"trigger\": 'item'\n\t\t},\n\t\t\"grid\": {\n\t\t\t\"top\": 40,\n\t\t\t\"bottom\": 30,\n\t\t\t\"right\": 15,\n\t\t\t\"left\": 15\n\t\t},\n\t\t\"legend\": {\n\t\t\t\"bottom\": 'left',\n\t\t},\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '',\n\t\t\t\"type\": 'pie',\n\t\t\t\"data\": [],\n\t\t\t\"radius\": '50%',\n\t\t\t\"label\": {\n\t\t\t\t\"show\": true,\n        \"color\": \"#666666\",\n\t\t\t\t\"position\": 'top',\n\t\t\t},\n\t\t},\n\t},\n\t\"ring\": {\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"text\": ''\n\t\t},\n\t\t\"tooltip\": {\n\t\t\t\"trigger\": 'item'\n\t\t},\n\t\t\"grid\": {\n\t\t\t\"top\": 40,\n\t\t\t\"bottom\": 30,\n\t\t\t\"right\": 15,\n\t\t\t\"left\": 15\n\t\t},\n\t\t\"legend\": {\n\t\t\t\"bottom\": 'left',\n\t\t},\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '',\n\t\t\t\"type\": 'pie',\n\t\t\t\"data\": [],\n\t\t\t\"radius\": ['40%', '70%'],\n\t\t\t\"avoidLabelOverlap\": false,\n\t\t\t\"label\": {\n\t\t\t\t\"show\": true,\n        \"color\": \"#666666\",\n\t\t\t\t\"position\": 'top',\n\t\t\t},\n\t\t\t\"labelLine\": {\n\t\t\t\t\"show\": true\n\t\t\t},\n\t\t},\n\t},\n\t\"rose\": {\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"text\": ''\n\t\t},\n\t\t\"tooltip\": {\n\t\t\t\"trigger\": 'item'\n\t\t},\n\t\t\"legend\": {\n\t\t\t\"top\": 'bottom'\n\t\t},\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '',\n\t\t\t\"type\": 'pie',\n\t\t\t\"data\": [],\n\t\t\t\"radius\": \"55%\",\n\t\t\t\"center\": ['50%', '50%'],\n\t\t\t\"roseType\": 'area',\n\t\t},\n\t},\n\t\"funnel\": {\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"text\": ''\n\t\t},\n\t\t\"tooltip\": {\n\t\t\t\"trigger\": 'item',\n\t\t\t\"formatter\": \"{b} : {c}%\"\n\t\t},\n\t\t\"legend\": {\n\t\t\t\"top\": 'bottom'\n\t\t},\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '',\n\t\t\t\"type\": 'funnel',\n\t\t\t\"left\": '10%',\n\t\t\t\"top\": 60,\n\t\t\t\"bottom\": 60,\n\t\t\t\"width\": '80%',\n\t\t\t\"min\": 0,\n\t\t\t\"max\": 100,\n\t\t\t\"minSize\": '0%',\n\t\t\t\"maxSize\": '100%',\n\t\t\t\"sort\": 'descending',\n\t\t\t\"gap\": 2,\n\t\t\t\"label\": {\n\t\t\t\t\"show\": true,\n\t\t\t\t\"position\": 'inside'\n\t\t\t},\n\t\t\t\"labelLine\": {\n\t\t\t\t\"length\": 10,\n\t\t\t\t\"lineStyle\": {\n\t\t\t\t\t\"width\": 1,\n\t\t\t\t\t\"type\": 'solid'\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"itemStyle\": {\n\t\t\t\t\"bordercolor\": '#fff',\n\t\t\t\t\"borderwidth\": 1\n\t\t\t},\n\t\t\t\"emphasis\": {\n\t\t\t\t\"label\": {\n\t\t\t\t\t\"fontSize\": 20\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"data\": [],\n\t\t},\n\t},\n\t\"gauge\": {\n\t\t\"color\": color,\n\t\t\"tooltip\": {\n        \"formatter\": '{a} <br/>{b} : {c}%'\n    },\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '业务指标',\n      \"type\": 'gauge',\n      \"detail\": {\"formatter\": '{value}%'},\n      \"data\": [{\"value\": 50, \"name\": '完成率'}]\n\t\t},\n\t},\n\t\"candle\": {\n\t\t\"xAxis\": {\n\t\t\t\"data\": []\n\t\t},\n\t\t\"yAxis\": {},\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"text\": ''\n\t\t},\n\t\t\"dataZoom\": [{\n\t\t\t\t\"type\": 'inside',\n\t\t\t\t\"xAxisIndex\": [0, 1],\n\t\t\t\t\"start\": 10,\n\t\t\t\t\"end\": 100\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"show\": true,\n\t\t\t\t\"xAxisIndex\": [0, 1],\n\t\t\t\t\"type\": 'slider',\n\t\t\t\t\"bottom\": 10,\n\t\t\t\t\"start\": 10,\n\t\t\t\t\"end\": 100\n\t\t\t}\n\t\t],\n\t\t\"seriesTemplate\": {\n\t\t\t\"name\": '',\n\t\t\t\"type\": 'k',\n\t\t\t\"data\": [],\n\t\t},\n\t}\n}\n\nexport default cfe;"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/js_sdk/u-charts/config-ucharts.js",
    "content": "/*\n * uCharts®\n * 高性能跨平台图表库，支持H5、APP、小程序（微信/支付宝/百度/头条/QQ/360）、Vue、Taro等支持canvas的框架平台\n * Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved.\n * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n * 复制使用请保留本段注释，感谢支持开源！\n * \n * uCharts®官方网站\n * https://www.uCharts.cn\n * \n * 开源地址:\n * https://gitee.com/uCharts/uCharts\n * \n * uni-app插件市场地址：\n * http://ext.dcloud.net.cn/plugin?id=271\n * \n */\n\n// 主题颜色配置：如每个图表类型需要不同主题，请在对应图表类型上更改color属性\nconst color = ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'];\n\n//事件转换函数，主要用作格式化x轴为时间轴，根据需求自行修改\nconst formatDateTime = (timeStamp, returnType)=>{\n  var date = new Date();\n  date.setTime(timeStamp * 1000);\n  var y = date.getFullYear();\n  var m = date.getMonth() + 1;\n  m = m < 10 ? ('0' + m) : m;\n  var d = date.getDate();\n  d = d < 10 ? ('0' + d) : d;\n  var h = date.getHours();\n  h = h < 10 ? ('0' + h) : h;\n  var minute = date.getMinutes();\n  var second = date.getSeconds();\n  minute = minute < 10 ? ('0' + minute) : minute;\n  second = second < 10 ? ('0' + second) : second;\n  if(returnType == 'full'){return y + '-' + m + '-' + d + ' '+ h +':' + minute + ':' + second;}\n  if(returnType == 'y-m-d'){return y + '-' + m + '-' + d;}\n  if(returnType == 'h:m'){return  h +':' + minute;}\n  if(returnType == 'h:m:s'){return  h +':' + minute +':' + second;}\n  return [y, m, d, h, minute, second];\n}\n\nconst cfu = {\n  //demotype为自定义图表类型，一般不需要自定义图表类型，只需要改根节点上对应的类型即可\n\t\"type\":[\"pie\",\"ring\",\"rose\",\"word\",\"funnel\",\"map\",\"arcbar\",\"line\",\"column\",\"mount\",\"bar\",\"area\",\"radar\",\"gauge\",\"candle\",\"mix\",\"tline\",\"tarea\",\"scatter\",\"bubble\",\"demotype\"],\n\t\"range\":[\"饼状图\",\"圆环图\",\"玫瑰图\",\"词云图\",\"漏斗图\",\"地图\",\"圆弧进度条\",\"折线图\",\"柱状图\",\"山峰图\",\"条状图\",\"区域图\",\"雷达图\",\"仪表盘\",\"K线图\",\"混合图\",\"时间轴折线\",\"时间轴区域\",\"散点图\",\"气泡图\",\"自定义类型\"],\n  //增加自定义图表类型，如果需要categories，请在这里加入您的图表类型，例如最后的\"demotype\"\n  //自定义类型时需要注意\"tline\",\"tarea\",\"scatter\",\"bubble\"等时间轴（矢量x轴）类图表，没有categories，不需要加入categories\n\t\"categories\":[\"line\",\"column\",\"mount\",\"bar\",\"area\",\"radar\",\"gauge\",\"candle\",\"mix\",\"demotype\"],\n  //instance为实例变量承载属性，不要删除\n  \"instance\":{},\n  //option为opts及eopts承载属性，不要删除\n  \"option\":{},\n  //下面是自定义format配置，因除H5端外的其他端无法通过props传递函数，只能通过此属性对应下标的方式来替换\n  \"formatter\":{\n    \"yAxisDemo1\":function(val, index, opts){return val+'元'},\n    \"yAxisDemo2\":function(val, index, opts){return val.toFixed(2)},\n    \"xAxisDemo1\":function(val, index, opts){return val+'年';},\n    \"xAxisDemo2\":function(val, index, opts){return formatDateTime(val,'h:m')},\n    \"seriesDemo1\":function(val, index, series, opts){return val+'元'},\n    \"tooltipDemo1\":function(item, category, index, opts){\n      if(index==0){\n      \treturn '随便用'+item.data+'年'\n      }else{\n      \treturn '其他我没改'+item.data+'天'\n      }\n    },\n    \"pieDemo\":function(val, index, series, opts){\n      if(index !== undefined){\n        return series[index].name+'：'+series[index].data+'元'\n      }\n    },\n  },\n  //这里演示了自定义您的图表类型的option，可以随意命名，之后在组件上 type=\"demotype\" 后，组件会调用这个花括号里的option，如果组件上还存在opts参数，会将demotype与opts中option合并后渲染图表。\n  \"demotype\":{\n    //我这里把曲线图当做了自定义图表类型，您可以根据需要随意指定类型或配置\n    \"type\": \"line\",\n    \"color\": color,\n    \"padding\": [15,10,0,15],\n    \"xAxis\": {\n      \"disableGrid\": true,\n    },\n    \"yAxis\": {\n      \"gridType\": \"dash\",\n      \"dashLength\": 2,\n    },\n    \"legend\": {\n    },\n    \"extra\": {\n    \t\"line\": {\n    \t\t\"type\": \"curve\",\n    \t\t\"width\": 2\n    \t},\n    }\n  },\n  //下面是自定义配置，请添加项目所需的通用配置\n\t\"pie\":{\n\t\t\"type\": \"pie\",\n    \"color\": color,\n\t\t\"padding\": [5,5,5,5],\n\t\t\"extra\": {\n\t\t\t\"pie\": {\n\t\t\t\t\"activeOpacity\": 0.5,\n\t\t\t\t\"activeRadius\": 10,\n\t\t\t\t\"offsetAngle\": 0,\n\t\t\t\t\"labelWidth\": 15,\n\t\t\t\t\"border\": true,\n\t\t\t\t\"borderWidth\": 3,\n\t\t\t\t\"borderColor\": \"#FFFFFF\"\n\t\t\t},\n\t\t}\n\t},\n\t\"ring\":{\n\t\t\"type\": \"ring\",\n    \"color\": color,\n\t\t\"padding\": [5,5,5,5],\n\t\t\"rotate\": false,\n\t\t\"dataLabel\": true,\n\t\t\"legend\": {\n\t\t\t\"show\": true,\n\t\t\t\"position\": \"right\",\n      \"lineHeight\": 25,\n\t\t},\n\t\t\"title\": {\n\t\t\t\"name\": \"收益率\",\n\t\t\t\"fontSize\": 15,\n\t\t\t\"color\": \"#666666\"\n\t\t},\n\t\t\"subtitle\": {\n\t\t\t\"name\": \"70%\",\n\t\t\t\"fontSize\": 25,\n\t\t\t\"color\": \"#7cb5ec\"\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"ring\": {\n\t\t\t\t\"ringWidth\":30,\n\t\t\t\t\"activeOpacity\": 0.5,\n\t\t\t\t\"activeRadius\": 10,\n\t\t\t\t\"offsetAngle\": 0,\n\t\t\t\t\"labelWidth\": 15,\n\t\t\t\t\"border\": true,\n\t\t\t\t\"borderWidth\": 3,\n\t\t\t\t\"borderColor\": \"#FFFFFF\"\n\t\t\t},\n\t\t},\n\t},\n\t\"rose\":{\n\t\t\"type\": \"rose\",\n    \"color\": color,\n\t\t\"padding\": [5,5,5,5],\n\t\t\"legend\": {\n\t\t\t\"show\": true,\n\t\t\t\"position\": \"left\",\n      \"lineHeight\": 25,\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"rose\": {\n\t\t\t\t\"type\": \"area\",\n\t\t\t\t\"minRadius\": 50,\n\t\t\t\t\"activeOpacity\": 0.5,\n\t\t\t\t\"activeRadius\": 10,\n\t\t\t\t\"offsetAngle\": 0,\n\t\t\t\t\"labelWidth\": 15,\n\t\t\t\t\"border\": false,\n\t\t\t\t\"borderWidth\": 2,\n\t\t\t\t\"borderColor\": \"#FFFFFF\"\n\t\t\t},\n\t\t}\n\t},\n\t\"word\":{\n\t\t\"type\": \"word\",\n    \"color\": color,\n\t\t\"extra\": {\n\t\t\t\"word\": {\n\t\t\t\t\"type\": \"normal\",\n\t\t\t\t\"autoColors\": false\n\t\t\t}\n\t\t}\n\t},\n\t\"funnel\":{\n\t\t\"type\": \"funnel\",\n    \"color\": color,\n\t\t\"padding\": [15,15,0,15],\n\t\t\"extra\": {\n\t\t\t\"funnel\": {\n\t\t\t\t\"activeOpacity\": 0.3,\n\t\t\t\t\"activeWidth\": 10,\n\t\t\t\t\"border\": true,\n\t\t\t\t\"borderWidth\": 2,\n\t\t\t\t\"borderColor\": \"#FFFFFF\",\n\t\t\t\t\"fillOpacity\": 1,\n\t\t\t\t\"labelAlign\": \"right\"\n\t\t\t},\n\t\t}\n\t},\n\t\"map\":{\n\t\t\"type\": \"map\",\n    \"color\": color,\n\t\t\"padding\": [0,0,0,0],\n    \"dataLabel\": true,\n\t\t\"extra\": {\n\t\t\t\"map\": {\n\t\t\t\t\"border\": true,\n\t\t\t\t\"borderWidth\": 1,\n\t\t\t\t\"borderColor\": \"#666666\",\n\t\t\t\t\"fillOpacity\": 0.6,\n\t\t\t\t\"activeBorderColor\": \"#F04864\",\n\t\t\t\t\"activeFillColor\": \"#FACC14\",\n\t\t\t\t\"activeFillOpacity\": 1\n\t\t\t},\n\t\t}\n\t},\n\t\"arcbar\":{\n\t\t\"type\": \"arcbar\",\n    \"color\": color,\n\t\t\"title\": {\n\t\t\t\"name\": \"百分比\",\n\t\t\t\"fontSize\": 25,\n\t\t\t\"color\": \"#00FF00\"\n\t\t},\n\t\t\"subtitle\": {\n\t\t\t\"name\": \"默认标题\",\n\t\t\t\"fontSize\": 15,\n\t\t\t\"color\": \"#666666\"\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"arcbar\": {\n\t\t\t\t\"type\": \"default\",\n\t\t\t\t\"width\": 12,\n\t\t\t\t\"backgroundColor\": \"#E9E9E9\",\n\t\t\t\t\"startAngle\": 0.75,\n\t\t\t\t\"endAngle\": 0.25,\n\t\t\t\t\"gap\": 2\n\t\t\t}\n\t\t}\n\t},\n\t\"line\":{\n\t\t\"type\": \"line\",\n    \"color\": color,\n\t\t\"padding\": [15,10,0,15],\n\t\t\"xAxis\": {\n      \"disableGrid\": true,\n\t\t},\n\t\t\"yAxis\": {\n      \"gridType\": \"dash\",\n      \"dashLength\": 2,\n\t\t},\n\t\t\"legend\": {\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"line\": {\n\t\t\t\t\"type\": \"straight\",\n\t\t\t\t\"width\": 2\n\t\t\t},\n\t\t}\n\t},\n  \"tline\":{\n  \t\"type\": \"line\",\n    \"color\": color,\n  \t\"padding\": [15,10,0,15],\n  \t\"xAxis\": {\n      \"disableGrid\": false,\n      \"boundaryGap\":\"justify\",\n  \t},\n  \t\"yAxis\": {\n      \"gridType\": \"dash\",\n      \"dashLength\": 2,\n      \"data\":[\n        {\n          \"min\":0,\n          \"max\":80\n        }\n      ]\n  \t},\n  \t\"legend\": {\n  \t},\n  \t\"extra\": {\n  \t\t\"line\": {\n  \t\t\t\"type\": \"curve\",\n  \t\t\t\"width\": 2\n  \t\t},\n  \t}\n  },\n  \"tarea\":{\n  \t\"type\": \"area\",\n    \"color\": color,\n  \t\"padding\": [15,10,0,15],\n  \t\"xAxis\": {\n      \"disableGrid\": true,\n      \"boundaryGap\":\"justify\",\n  \t},\n  \t\"yAxis\": {\n      \"gridType\": \"dash\",\n      \"dashLength\": 2,\n      \"data\":[\n        {\n          \"min\":0,\n          \"max\":80\n        }\n      ]\n  \t},\n  \t\"legend\": {\n  \t},\n  \t\"extra\": {\n  \t\t\"area\": {\n  \t\t\t\"type\": \"curve\",\n  \t\t\t\"opacity\": 0.2,\n  \t\t\t\"addLine\": true,\n  \t\t\t\"width\": 2,\n  \t\t\t\"gradient\": true\n  \t\t},\n  \t}\n  },\n\t\"column\":{\n\t\t\"type\": \"column\",\n    \"color\": color,\n\t\t\"padding\": [15,15,0,5],\n\t\t\"xAxis\": {\n      \"disableGrid\": true,\n\t\t},\n\t\t\"yAxis\": {\n      \"data\":[{\"min\":0}]\n\t\t},\n\t\t\"legend\": {\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"column\": {\n\t\t\t\t\"type\": \"group\",\n\t\t\t\t\"width\": 30,\n\t\t\t\t\"activeBgColor\": \"#000000\",\n\t\t\t\t\"activeBgOpacity\": 0.08\n\t\t\t},\n\t\t}\n\t},\n  \"mount\":{\n  \t\"type\": \"mount\",\n    \"color\": color,\n  \t\"padding\": [15,15,0,5],\n  \t\"xAxis\": {\n      \"disableGrid\": true,\n  \t},\n  \t\"yAxis\": {\n      \"data\":[{\"min\":0}]\n  \t},\n  \t\"legend\": {\n  \t},\n  \t\"extra\": {\n  \t\t\"mount\": {\n  \t\t\t\"type\": \"mount\",\n  \t\t\t\"widthRatio\": 1.5,\n  \t\t},\n  \t}\n  },\n  \"bar\":{\n  \t\"type\": \"bar\",\n    \"color\": color,\n  \t\"padding\": [15,30,0,5],\n  \t\"xAxis\": {\n      \"boundaryGap\":\"justify\",\n      \"disableGrid\":false,\n      \"min\":0,\n      \"axisLine\":false\n  \t},\n  \t\"yAxis\": {\n  \t},\n  \t\"legend\": {\n  \t},\n  \t\"extra\": {\n  \t\t\"bar\": {\n  \t\t\t\"type\": \"group\",\n  \t\t\t\"width\": 30,\n  \t\t\t\"meterBorde\": 1,\n  \t\t\t\"meterFillColor\": \"#FFFFFF\",\n  \t\t\t\"activeBgColor\": \"#000000\",\n  \t\t\t\"activeBgOpacity\": 0.08\n  \t\t},\n  \t}\n  },\n\t\"area\":{\n\t\t\"type\": \"area\",\n\t\t\"color\": color,\n\t\t\"padding\": [15,15,0,15],\n\t\t\"xAxis\": {\n      \"disableGrid\": true,\n\t\t},\n\t\t\"yAxis\": {\n      \"gridType\": \"dash\",\n      \"dashLength\": 2,\n\t\t},\n\t\t\"legend\": {\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"area\": {\n\t\t\t\t\"type\": \"straight\",\n\t\t\t\t\"opacity\": 0.2,\n\t\t\t\t\"addLine\": true,\n\t\t\t\t\"width\": 2,\n\t\t\t\t\"gradient\": false\n\t\t\t},\n\t\t}\n\t},\n\t\"radar\":{\n\t\t\"type\": \"radar\",\n\t\t\"color\": color,\n\t\t\"padding\": [5,5,5,5],\n    \"dataLabel\": false,\n\t\t\"legend\": {\n\t\t\t\"show\": true,\n\t\t\t\"position\": \"right\",\n      \"lineHeight\": 25,\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"radar\": {\n\t\t\t\t\"gridType\": \"radar\",\n\t\t\t\t\"gridColor\": \"#CCCCCC\",\n\t\t\t\t\"gridCount\": 3,\n\t\t\t\t\"opacity\": 0.2,\n\t\t\t\t\"max\": 200\n\t\t\t},\n\t\t}\n\t},\n\t\"gauge\":{\n\t\t\"type\": \"gauge\",\n\t\t\"color\": color,\n\t\t\"title\": {\n\t\t\t\"name\": \"66Km/H\",\n\t\t\t\"fontSize\": 25,\n\t\t\t\"color\": \"#2fc25b\",\n\t\t\t\"offsetY\": 50\n\t\t},\n\t\t\"subtitle\": {\n\t\t\t\"name\": \"实时速度\",\n\t\t\t\"fontSize\": 15,\n\t\t\t\"color\": \"#1890ff\",\n\t\t\t\"offsetY\": -50\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"gauge\": {\n\t\t\t\t\"type\": \"default\",\n\t\t\t\t\"width\": 30,\n\t\t\t\t\"labelColor\": \"#666666\",\n\t\t\t\t\"startAngle\": 0.75,\n\t\t\t\t\"endAngle\": 0.25,\n\t\t\t\t\"startNumber\": 0,\n\t\t\t\t\"endNumber\": 100,\n\t\t\t\t\"labelFormat\": \"\",\n\t\t\t\t\"splitLine\": {\n\t\t\t\t\t\"fixRadius\": 0,\n\t\t\t\t\t\"splitNumber\": 10,\n\t\t\t\t\t\"width\": 30,\n\t\t\t\t\t\"color\": \"#FFFFFF\",\n\t\t\t\t\t\"childNumber\": 5,\n\t\t\t\t\t\"childWidth\": 12\n\t\t\t\t},\n\t\t\t\t\"pointer\": {\n\t\t\t\t\t\"width\": 24,\n\t\t\t\t\t\"color\": \"auto\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\t\"candle\":{\n\t\t\"type\": \"candle\",\n\t\t\"color\": color,\n\t\t\"padding\": [15,15,0,15],\n\t\t\"enableScroll\": true,\n\t\t\"enableMarkLine\": true,\n\t\t\"dataLabel\": false,\n\t\t\"xAxis\": {\n\t\t\t\"labelCount\": 4,\n\t\t\t\"itemCount\": 40,\n\t\t\t\"disableGrid\": true,\n\t\t\t\"gridColor\": \"#CCCCCC\",\n\t\t\t\"gridType\": \"solid\",\n\t\t\t\"dashLength\": 4,\n\t\t\t\"scrollShow\": true,\n\t\t\t\"scrollAlign\": \"left\",\n\t\t\t\"scrollColor\": \"#A6A6A6\",\n\t\t\t\"scrollBackgroundColor\": \"#EFEBEF\"\n\t\t},\n\t\t\"yAxis\": {\n\t\t},\n\t\t\"legend\": {\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"candle\": {\n\t\t\t\t\"color\": {\n\t\t\t\t\t\"upLine\": \"#f04864\",\n\t\t\t\t\t\"upFill\": \"#f04864\",\n\t\t\t\t\t\"downLine\": \"#2fc25b\",\n\t\t\t\t\t\"downFill\": \"#2fc25b\"\n\t\t\t\t},\n\t\t\t\t\"average\": {\n\t\t\t\t\t\"show\": true,\n\t\t\t\t\t\"name\": [\"MA5\",\"MA10\",\"MA30\"],\n\t\t\t\t\t\"day\": [5,10,20],\n\t\t\t\t\t\"color\": [\"#1890ff\",\"#2fc25b\",\"#facc14\"]\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"markLine\": {\n\t\t\t\t\"type\": \"dash\",\n\t\t\t\t\"dashLength\": 5,\n\t\t\t\t\"data\": [\n\t\t\t\t\t{\n\t\t\t\t\t\t\"value\": 2150,\n\t\t\t\t\t\t\"lineColor\": \"#f04864\",\n\t\t\t\t\t\t\"showLabel\": true\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\t\"value\": 2350,\n\t\t\t\t\t\t\"lineColor\": \"#f04864\",\n\t\t\t\t\t\t\"showLabel\": true\n\t\t\t\t\t}\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t},\n\t\"mix\":{\n\t\t\"type\": \"mix\",\n\t\t\"color\": color,\n\t\t\"padding\": [15,15,0,15],\n\t\t\"xAxis\": {\n      \"disableGrid\": true,\n\t\t},\n\t\t\"yAxis\": {\n\t\t\t\"disabled\": false,\n\t\t\t\"disableGrid\": false,\n\t\t\t\"splitNumber\": 5,\n\t\t\t\"gridType\": \"dash\",\n\t\t\t\"dashLength\": 4,\n\t\t\t\"gridColor\": \"#CCCCCC\",\n\t\t\t\"padding\": 10,\n\t\t\t\"showTitle\": true,\n\t\t\t\"data\": []\n\t\t},\n\t\t\"legend\": {\n\t\t},\n\t\t\"extra\": {\n\t\t\t\"mix\": {\n\t\t\t\t\"column\": {\n\t\t\t\t\t\"width\": 20\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t},\n\t\"scatter\":{\n\t\t\"type\": \"scatter\",\n\t\t\"color\":color,\n\t\t\"padding\":[15,15,0,15],\n    \"dataLabel\":false,\n    \"xAxis\": {\n      \"disableGrid\": false,\n      \"gridType\":\"dash\",\n      \"splitNumber\":5,\n      \"boundaryGap\":\"justify\",\n      \"min\":0\n    },\n    \"yAxis\": {\n      \"disableGrid\": false,\n      \"gridType\":\"dash\",\n    },\n    \"legend\": {\n    },\n    \"extra\": {\n    \t\"scatter\": {\n    \t},\n    }\n\t},\n\t\"bubble\":{\n\t\t\"type\": \"bubble\",\n\t\t\"color\":color,\n\t\t\"padding\":[15,15,0,15],\n    \"xAxis\": {\n      \"disableGrid\": false,\n      \"gridType\":\"dash\",\n      \"splitNumber\":5,\n      \"boundaryGap\":\"justify\",\n      \"min\":0,\n      \"max\":250\n    },\n    \"yAxis\": {\n      \"disableGrid\": false,\n      \"gridType\":\"dash\",\n      \"data\":[{\n        \"min\":0,\n        \"max\":150\n      }]\n    },\n    \"legend\": {\n    },\n    \"extra\": {\n    \t\"bubble\": {\n        \"border\":2,\n        \"opacity\": 0.5,\n    \t},\n    }\n\t}\n}\n\nexport default cfu;"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/js_sdk/u-charts/readme.md",
    "content": "# uCharts JSSDK说明\n1、如不使用uCharts组件，可直接引用u-charts.js，打包编译后会`自动压缩`，压缩后体积约为`120kb`。\n2、如果120kb的体积仍需压缩，请手到uCharts官网通过在线定制选择您需要的图表。\n3、config-ucharts.js为uCharts组件的用户配置文件，升级前请`自行备份config-ucharts.js`文件，以免被强制覆盖。\n4、config-echarts.js为ECharts组件的用户配置文件，升级前请`自行备份config-echarts.js`文件，以免被强制覆盖。"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/js_sdk/u-charts/u-charts.js",
    "content": "/*\n * uCharts (R)\n * 高性能跨平台图表库，支持H5、APP、小程序（微信/支付宝/百度/头条/QQ/360/快手）、Vue、Taro等支持canvas的框架平台\n * Copyright (C) 2018-2022 QIUN (R) 秋云 https://www.ucharts.cn All rights reserved.\n * Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )\n * 复制使用请保留本段注释，感谢支持开源！\n * \n * uCharts (R) 官方网站\n * https://www.uCharts.cn\n * \n * 开源地址:\n * https://gitee.com/uCharts/uCharts\n * \n * uni-app插件市场地址：\n * http://ext.dcloud.net.cn/plugin?id=271\n * \n */\n\n'use strict';\n\nvar config = {\n  version: 'v2.4.3-20220505',\n  yAxisWidth: 15,\n  xAxisHeight: 22,\n  xAxisTextPadding: 3,\n  padding: [10, 10, 10, 10],\n  pixelRatio: 1,\n  rotate: false,\n  fontSize: 13,\n  fontColor: '#666666',\n  dataPointShape: ['circle', 'circle', 'circle', 'circle'],\n  color: ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc'],\n  linearColor: ['#0EE2F8', '#2BDCA8', '#FA7D8D', '#EB88E2', '#2AE3A0', '#0EE2F8', '#EB88E2', '#6773E3', '#F78A85'],\n  pieChartLinePadding: 15,\n  pieChartTextPadding: 5,\n  titleFontSize: 20,\n  subtitleFontSize: 15,\n  toolTipPadding: 3,\n  toolTipBackground: '#000000',\n  toolTipOpacity: 0.7,\n  toolTipLineHeight: 20,\n  radarLabelTextMargin: 13,\n};\n\nvar assign = function(target, ...varArgs) {\n  if (target == null) {\n    throw new TypeError('[uCharts] Cannot convert undefined or null to object');\n  }\n  if (!varArgs || varArgs.length <= 0) {\n    return target;\n  }\n  // 深度合并对象\n  function deepAssign(obj1, obj2) {\n    for (let key in obj2) {\n      obj1[key] = obj1[key] && obj1[key].toString() === \"[object Object]\" ?\n        deepAssign(obj1[key], obj2[key]) : obj1[key] = obj2[key];\n    }\n    return obj1;\n  }\n  varArgs.forEach(val => {\n    target = deepAssign(target, val);\n  });\n  return target;\n};\n\nvar util = {\n  toFixed: function toFixed(num, limit) {\n    limit = limit || 2;\n    if (this.isFloat(num)) {\n      num = num.toFixed(limit);\n    }\n    return num;\n  },\n  isFloat: function isFloat(num) {\n    return num % 1 !== 0;\n  },\n  approximatelyEqual: function approximatelyEqual(num1, num2) {\n    return Math.abs(num1 - num2) < 1e-10;\n  },\n  isSameSign: function isSameSign(num1, num2) {\n    return Math.abs(num1) === num1 && Math.abs(num2) === num2 || Math.abs(num1) !== num1 && Math.abs(num2) !== num2;\n  },\n  isSameXCoordinateArea: function isSameXCoordinateArea(p1, p2) {\n    return this.isSameSign(p1.x, p2.x);\n  },\n  isCollision: function isCollision(obj1, obj2) {\n    obj1.end = {};\n    obj1.end.x = obj1.start.x + obj1.width;\n    obj1.end.y = obj1.start.y - obj1.height;\n    obj2.end = {};\n    obj2.end.x = obj2.start.x + obj2.width;\n    obj2.end.y = obj2.start.y - obj2.height;\n    var flag = obj2.start.x > obj1.end.x || obj2.end.x < obj1.start.x || obj2.end.y > obj1.start.y || obj2.start.y < obj1.end.y;\n    return !flag;\n  }\n};\n\n//兼容H5点击事件\nfunction getH5Offset(e) {\n  e.mp = {\n    changedTouches: []\n  };\n  e.mp.changedTouches.push({\n    x: e.offsetX,\n    y: e.offsetY\n  });\n  return e;\n}\n\n// hex 转 rgba\nfunction hexToRgb(hexValue, opc) {\n  var rgx = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;\n  var hex = hexValue.replace(rgx, function(m, r, g, b) {\n    return r + r + g + g + b + b;\n  });\n  var rgb = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n  var r = parseInt(rgb[1], 16);\n  var g = parseInt(rgb[2], 16);\n  var b = parseInt(rgb[3], 16);\n  return 'rgba(' + r + ',' + g + ',' + b + ',' + opc + ')';\n}\n\nfunction findRange(num, type, limit) {\n  if (isNaN(num)) {\n    throw new Error('[uCharts] series数据需为Number格式');\n  }\n  limit = limit || 10;\n  type = type ? type : 'upper';\n  var multiple = 1;\n  while (limit < 1) {\n    limit *= 10;\n    multiple *= 10;\n  }\n  if (type === 'upper') {\n    num = Math.ceil(num * multiple);\n  } else {\n    num = Math.floor(num * multiple);\n  }\n  while (num % limit !== 0) {\n    if (type === 'upper') {\n      if (num == num + 1) { //修复数据值过大num++无效的bug by 向日葵 @xrk_jy\n        break;\n      }\n      num++;\n    } else {\n      num--;\n    }\n  }\n  return num / multiple;\n}\n\nfunction calCandleMA(dayArr, nameArr, colorArr, kdata) {\n  let seriesTemp = [];\n  for (let k = 0; k < dayArr.length; k++) {\n    let seriesItem = {\n      data: [],\n      name: nameArr[k],\n      color: colorArr[k]\n    };\n    for (let i = 0, len = kdata.length; i < len; i++) {\n      if (i < dayArr[k]) {\n        seriesItem.data.push(null);\n        continue;\n      }\n      let sum = 0;\n      for (let j = 0; j < dayArr[k]; j++) {\n        sum += kdata[i - j][1];\n      }\n      seriesItem.data.push(+(sum / dayArr[k]).toFixed(3));\n    }\n    seriesTemp.push(seriesItem);\n  }\n  return seriesTemp;\n}\n\nfunction calValidDistance(self, distance, chartData, config, opts) {\n  var dataChartAreaWidth = opts.width - opts.area[1] - opts.area[3];\n  var dataChartWidth = chartData.eachSpacing * (opts.chartData.xAxisData.xAxisPoints.length - 1);\n  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){\n    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2\n    dataChartWidth += (opts.extra.mount.widthRatio - 1)*chartData.eachSpacing;\n  }\n  var validDistance = distance;\n  if (distance >= 0) {\n    validDistance = 0;\n    self.uevent.trigger('scrollLeft');\n    self.scrollOption.position = 'left'\n    opts.xAxis.scrollPosition = 'left';\n  } else if (Math.abs(distance) >= dataChartWidth - dataChartAreaWidth) {\n    validDistance = dataChartAreaWidth - dataChartWidth;\n    self.uevent.trigger('scrollRight');\n    self.scrollOption.position = 'right'\n    opts.xAxis.scrollPosition = 'right';\n  } else {\n    self.scrollOption.position = distance\n    opts.xAxis.scrollPosition = distance;\n  }\n  return validDistance;\n}\n\nfunction isInAngleRange(angle, startAngle, endAngle) {\n  function adjust(angle) {\n    while (angle < 0) {\n      angle += 2 * Math.PI;\n    }\n    while (angle > 2 * Math.PI) {\n      angle -= 2 * Math.PI;\n    }\n    return angle;\n  }\n  angle = adjust(angle);\n  startAngle = adjust(startAngle);\n  endAngle = adjust(endAngle);\n  if (startAngle > endAngle) {\n    endAngle += 2 * Math.PI;\n    if (angle < startAngle) {\n      angle += 2 * Math.PI;\n    }\n  }\n  return angle >= startAngle && angle <= endAngle;\n}\n\nfunction createCurveControlPoints(points, i) {\n  function isNotMiddlePoint(points, i) {\n    if (points[i - 1] && points[i + 1]) {\n      return points[i].y >= Math.max(points[i - 1].y, points[i + 1].y) || points[i].y <= Math.min(points[i - 1].y,\n        points[i + 1].y);\n    } else {\n      return false;\n    }\n  }\n  function isNotMiddlePointX(points, i) {\n    if (points[i - 1] && points[i + 1]) {\n      return points[i].x >= Math.max(points[i - 1].x, points[i + 1].x) || points[i].x <= Math.min(points[i - 1].x,\n        points[i + 1].x);\n    } else {\n      return false;\n    }\n  }\n  var a = 0.2;\n  var b = 0.2;\n  var pAx = null;\n  var pAy = null;\n  var pBx = null;\n  var pBy = null;\n  if (i < 1) {\n    pAx = points[0].x + (points[1].x - points[0].x) * a;\n    pAy = points[0].y + (points[1].y - points[0].y) * a;\n  } else {\n    pAx = points[i].x + (points[i + 1].x - points[i - 1].x) * a;\n    pAy = points[i].y + (points[i + 1].y - points[i - 1].y) * a;\n  }\n\n  if (i > points.length - 3) {\n    var last = points.length - 1;\n    pBx = points[last].x - (points[last].x - points[last - 1].x) * b;\n    pBy = points[last].y - (points[last].y - points[last - 1].y) * b;\n  } else {\n    pBx = points[i + 1].x - (points[i + 2].x - points[i].x) * b;\n    pBy = points[i + 1].y - (points[i + 2].y - points[i].y) * b;\n  }\n  if (isNotMiddlePoint(points, i + 1)) {\n    pBy = points[i + 1].y;\n  }\n  if (isNotMiddlePoint(points, i)) {\n    pAy = points[i].y;\n  }\n  if (isNotMiddlePointX(points, i + 1)) {\n    pBx = points[i + 1].x;\n  }\n  if (isNotMiddlePointX(points, i)) {\n    pAx = points[i].x;\n  }\n  if (pAy >= Math.max(points[i].y, points[i + 1].y) || pAy <= Math.min(points[i].y, points[i + 1].y)) {\n    pAy = points[i].y;\n  }\n  if (pBy >= Math.max(points[i].y, points[i + 1].y) || pBy <= Math.min(points[i].y, points[i + 1].y)) {\n    pBy = points[i + 1].y;\n  }\n  if (pAx >= Math.max(points[i].x, points[i + 1].x) || pAx <= Math.min(points[i].x, points[i + 1].x)) {\n    pAx = points[i].x;\n  }\n  if (pBx >= Math.max(points[i].x, points[i + 1].x) || pBx <= Math.min(points[i].x, points[i + 1].x)) {\n    pBx = points[i + 1].x;\n  }\n  return {\n    ctrA: {\n      x: pAx,\n      y: pAy\n    },\n    ctrB: {\n      x: pBx,\n      y: pBy\n    }\n  };\n}\n\n\nfunction convertCoordinateOrigin(x, y, center) {\n  return {\n    x: center.x + x,\n    y: center.y - y\n  };\n}\n\nfunction avoidCollision(obj, target) {\n  if (target) {\n    // is collision test\n    while (util.isCollision(obj, target)) {\n      if (obj.start.x > 0) {\n        obj.start.y--;\n      } else if (obj.start.x < 0) {\n        obj.start.y++;\n      } else {\n        if (obj.start.y > 0) {\n          obj.start.y++;\n        } else {\n          obj.start.y--;\n        }\n      }\n    }\n  }\n  return obj;\n}\n\nfunction fixPieSeries(series, opts, config){\n  let pieSeriesArr = [];\n  if(series.length>0 && series[0].data.constructor.toString().indexOf('Array') > -1){\n    opts._pieSeries_ = series;\n    let oldseries = series[0].data;\n    for (var i = 0; i < oldseries.length; i++) {\n      oldseries[i].formatter = series[0].formatter;\n      oldseries[i].data = oldseries[i].value;\n      pieSeriesArr.push(oldseries[i]);\n    }\n    opts.series = pieSeriesArr;\n  }else{\n    pieSeriesArr = series;\n  }\n  return pieSeriesArr;\n}\n\nfunction fillSeries(series, opts, config) {\n  var index = 0;\n  for (var i = 0; i < series.length; i++) {\n    let item = series[i];\n    if (!item.color) {\n      item.color = config.color[index];\n      index = (index + 1) % config.color.length;\n    }\n    if (!item.linearIndex) {\n      item.linearIndex = i;\n    }\n    if (!item.index) {\n      item.index = 0;\n    }\n    if (!item.type) {\n      item.type = opts.type;\n    }\n    if (typeof item.show == \"undefined\") {\n      item.show = true;\n    }\n    if (!item.type) {\n      item.type = opts.type;\n    }\n    if (!item.pointShape) {\n      item.pointShape = \"circle\";\n    }\n    if (!item.legendShape) {\n      switch (item.type) {\n        case 'line':\n          item.legendShape = \"line\";\n          break;\n        case 'column':\n        case 'bar':\n          item.legendShape = \"rect\";\n          break;\n        case 'area':\n        case 'mount':\n          item.legendShape = \"triangle\";\n          break;\n        default:\n          item.legendShape = \"circle\";\n      }\n    }\n  }\n  return series;\n}\n\nfunction fillCustomColor(linearType, customColor, series, config) {\n  var newcolor = customColor || [];\n  if (linearType == 'custom' && newcolor.length == 0 ) {\n    newcolor = config.linearColor;\n  }\n  if (linearType == 'custom' && newcolor.length < series.length) {\n    let chazhi = series.length - newcolor.length;\n    for (var i = 0; i < chazhi; i++) {\n      newcolor.push(config.linearColor[(i + 1) % config.linearColor.length]);\n    }\n  }\n  return newcolor;\n}\n\nfunction getDataRange(minData, maxData) {\n  var limit = 0;\n  var range = maxData - minData;\n  if (range >= 10000) {\n    limit = 1000;\n  } else if (range >= 1000) {\n    limit = 100;\n  } else if (range >= 100) {\n    limit = 10;\n  } else if (range >= 10) {\n    limit = 5;\n  } else if (range >= 1) {\n    limit = 1;\n  } else if (range >= 0.1) {\n    limit = 0.1;\n  } else if (range >= 0.01) {\n    limit = 0.01;\n  } else if (range >= 0.001) {\n    limit = 0.001;\n  } else if (range >= 0.0001) {\n    limit = 0.0001;\n  } else if (range >= 0.00001) {\n    limit = 0.00001;\n  } else {\n    limit = 0.000001;\n  }\n  return {\n    minRange: findRange(minData, 'lower', limit),\n    maxRange: findRange(maxData, 'upper', limit)\n  };\n}\n\nfunction measureText(text, fontSize, context) {\n  var width = 0;\n  text = String(text);\n  // #ifdef MP-ALIPAY || MP-BAIDU || APP-NVUE\n  context = false;\n  // #endif\n  if (context !== false && context !== undefined && context.setFontSize && context.measureText) {\n    context.setFontSize(fontSize);\n    return context.measureText(text).width;\n  } else {\n    var text = text.split('');\n    for (let i = 0; i < text.length; i++) {\n      let item = text[i];\n      if (/[a-zA-Z]/.test(item)) {\n        width += 7;\n      } else if (/[0-9]/.test(item)) {\n        width += 5.5;\n      } else if (/\\./.test(item)) {\n        width += 2.7;\n      } else if (/-/.test(item)) {\n        width += 3.25;\n      } else if (/:/.test(item)) {\n        width += 2.5;\n      } else if (/[\\u4e00-\\u9fa5]/.test(item)) {\n        width += 10;\n      } else if (/\\(|\\)/.test(item)) {\n        width += 3.73;\n      } else if (/\\s/.test(item)) {\n        width += 2.5;\n      } else if (/%/.test(item)) {\n        width += 8;\n      } else {\n        width += 10;\n      }\n    }\n    return width * fontSize / 10;\n  }\n}\n\nfunction dataCombine(series) {\n  return series.reduce(function(a, b) {\n    return (a.data ? a.data : a).concat(b.data);\n  }, []);\n}\n\nfunction dataCombineStack(series, len) {\n  var sum = new Array(len);\n  for (var j = 0; j < sum.length; j++) {\n    sum[j] = 0;\n  }\n  for (var i = 0; i < series.length; i++) {\n    for (var j = 0; j < sum.length; j++) {\n      sum[j] += series[i].data[j];\n    }\n  }\n  return series.reduce(function(a, b) {\n    return (a.data ? a.data : a).concat(b.data).concat(sum);\n  }, []);\n}\n\nfunction getTouches(touches, opts, e) {\n  let x, y;\n  if (touches.clientX) {\n    if (opts.rotate) {\n      y = opts.height - touches.clientX * opts.pix;\n      x = (touches.pageY - e.currentTarget.offsetTop - (opts.height / opts.pix / 2) * (opts.pix - 1)) * opts.pix;\n    } else {\n      x = touches.clientX * opts.pix;\n      y = (touches.pageY - e.currentTarget.offsetTop - (opts.height / opts.pix / 2) * (opts.pix - 1)) * opts.pix;\n    }\n  } else {\n    if (opts.rotate) {\n      y = opts.height - touches.x * opts.pix;\n      x = touches.y * opts.pix;\n    } else {\n      x = touches.x * opts.pix;\n      y = touches.y * opts.pix;\n    }\n  }\n  return {\n    x: x,\n    y: y\n  }\n}\n\nfunction getSeriesDataItem(series, index, group) {\n  var data = [];\n  var newSeries = [];\n  var indexIsArr = index.constructor.toString().indexOf('Array') > -1;\n  if(indexIsArr){\n    let tempSeries = filterSeries(series);\n    for (var i = 0; i < group.length; i++) {\n      newSeries.push(tempSeries[group[i]]);\n    }\n  }else{\n    newSeries = series;\n  };\n  for (let i = 0; i < newSeries.length; i++) {\n    let item = newSeries[i];\n    let tmpindex = -1;\n    if(indexIsArr){\n      tmpindex = index[i];\n    }else{\n      tmpindex = index;\n    }\n    if (item.data[tmpindex] !== null && typeof item.data[tmpindex] !== 'undefined' && item.show) {\n      let seriesItem = {};\n      seriesItem.color = item.color;\n      seriesItem.type = item.type;\n      seriesItem.style = item.style;\n      seriesItem.pointShape = item.pointShape;\n      seriesItem.disableLegend = item.disableLegend;\n      seriesItem.name = item.name;\n      seriesItem.show = item.show;\n      seriesItem.data = item.formatter ? item.formatter(item.data[tmpindex]) : item.data[tmpindex];\n      data.push(seriesItem);\n    }\n  }\n  return data;\n}\n\nfunction getMaxTextListLength(list, fontSize, context) {\n  var lengthList = list.map(function(item) {\n    return measureText(item, fontSize, context);\n  });\n  return Math.max.apply(null, lengthList);\n}\n\nfunction getRadarCoordinateSeries(length) {\n  var eachAngle = 2 * Math.PI / length;\n  var CoordinateSeries = [];\n  for (var i = 0; i < length; i++) {\n    CoordinateSeries.push(eachAngle * i);\n  }\n  return CoordinateSeries.map(function(item) {\n    return -1 * item + Math.PI / 2;\n  });\n}\n\nfunction getToolTipData(seriesData, opts, index, group, categories) {\n  var option = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};\n  var calPoints = opts.chartData.calPoints?opts.chartData.calPoints:[];\n  let points = {};\n  if(group.length > 0){\n    let filterPoints = [];\n    for (let i = 0; i < group.length; i++) {\n      filterPoints.push(calPoints[group[i]])\n    }\n    points = filterPoints[0][index[0]];\n  }else{\n    for (let i = 0; i < calPoints.length; i++) {\n      if(calPoints[i][index]){\n        points = calPoints[i][index];\n        break;\n      }\n    }\n  };\n  var textList = seriesData.map(function(item) {\n    let titleText = null;\n    if (opts.categories && opts.categories.length>0) {\n      titleText = categories[index];\n    };\n    return {\n      text: option.formatter ? option.formatter(item, titleText, index, opts) : item.name + ': ' + item.data,\n      color: item.color\n    };\n  });\n  var offset = {\n    x: Math.round(points.x),\n    y: Math.round(points.y)\n  };\n  return {\n    textList: textList,\n    offset: offset\n  };\n}\n\nfunction getMixToolTipData(seriesData, opts, index, categories) {\n  var option = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};\n  var points = opts.chartData.xAxisPoints[index] + opts.chartData.eachSpacing / 2;\n  var textList = seriesData.map(function(item) {\n    return {\n      text: option.formatter ? option.formatter(item, categories[index], index, opts) : item.name + ': ' + item.data,\n      color: item.color,\n      disableLegend: item.disableLegend ? true : false\n    };\n  });\n  textList = textList.filter(function(item) {\n    if (item.disableLegend !== true) {\n      return item;\n    }\n  });\n  var offset = {\n    x: Math.round(points),\n    y: 0\n  };\n  return {\n    textList: textList,\n    offset: offset\n  };\n}\n\nfunction getCandleToolTipData(series, seriesData, opts, index, categories, extra) {\n  var option = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : {};\n  var calPoints = opts.chartData.calPoints;\n  let upColor = extra.color.upFill;\n  let downColor = extra.color.downFill;\n  //颜色顺序为开盘，收盘，最低，最高\n  let color = [upColor, upColor, downColor, upColor];\n  var textList = [];\n  seriesData.map(function(item) {\n    if (index == 0) {\n      if (item.data[1] - item.data[0] < 0) {\n        color[1] = downColor;\n      } else {\n        color[1] = upColor;\n      }\n    } else {\n      if (item.data[0] < series[index - 1][1]) {\n        color[0] = downColor;\n      }\n      if (item.data[1] < item.data[0]) {\n        color[1] = downColor;\n      }\n      if (item.data[2] > series[index - 1][1]) {\n        color[2] = upColor;\n      }\n      if (item.data[3] < series[index - 1][1]) {\n        color[3] = downColor;\n      }\n    }\n    let text1 = {\n      text: '开盘：' + item.data[0],\n      color: color[0]\n    };\n    let text2 = {\n      text: '收盘：' + item.data[1],\n      color: color[1]\n    };\n    let text3 = {\n      text: '最低：' + item.data[2],\n      color: color[2]\n    };\n    let text4 = {\n      text: '最高：' + item.data[3],\n      color: color[3]\n    };\n    textList.push(text1, text2, text3, text4);\n  });\n  var validCalPoints = [];\n  var offset = {\n    x: 0,\n    y: 0\n  };\n  for (let i = 0; i < calPoints.length; i++) {\n    let points = calPoints[i];\n    if (typeof points[index] !== 'undefined' && points[index] !== null) {\n      validCalPoints.push(points[index]);\n    }\n  }\n  offset.x = Math.round(validCalPoints[0][0].x);\n  return {\n    textList: textList,\n    offset: offset\n  };\n}\n\nfunction filterSeries(series) {\n  let tempSeries = [];\n  for (let i = 0; i < series.length; i++) {\n    if (series[i].show == true) {\n      tempSeries.push(series[i])\n    }\n  }\n  return tempSeries;\n}\n\nfunction findCurrentIndex(currentPoints, calPoints, opts, config) {\n  var offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;\n  var current={ index:-1, group:[] };\n  var spacing = opts.chartData.eachSpacing / 2;\n  let xAxisPoints = [];\n  if (calPoints && calPoints.length > 0) {\n    if (!opts.categories) {\n      spacing = 0;\n    }else{\n      for (let i = 1; i < opts.chartData.xAxisPoints.length; i++) {\n        xAxisPoints.push(opts.chartData.xAxisPoints[i] - spacing);\n      }\n      if ((opts.type == 'line' || opts.type == 'area') && opts.xAxis.boundaryGap == 'justify') {\n        xAxisPoints = opts.chartData.xAxisPoints;\n      }\n    }\n    if (isInExactChartArea(currentPoints, opts, config)) {\n      if (!opts.categories) {\n        let timePoints = Array(calPoints.length);\n        for (let i = 0; i < calPoints.length; i++) {\n          timePoints[i] = Array(calPoints[i].length)\n          for (let j = 0; j < calPoints[i].length; j++) {\n            timePoints[i][j] = (Math.abs(calPoints[i][j].x - currentPoints.x));\n          }\n        };\n        let pointValue =  Array(timePoints.length);\n        let pointIndex =  Array(timePoints.length);\n        for (let i = 0; i < timePoints.length; i++) {\n          pointValue[i] = Math.min.apply(null, timePoints[i]);\n          pointIndex[i] = timePoints[i].indexOf(pointValue[i]);\n        }\n        let minValue = Math.min.apply(null, pointValue);\n        current.index = [];\n        for (let i = 0; i < pointValue.length; i++) {\n          if(pointValue[i] == minValue){\n            current.group.push(i);\n            current.index.push(pointIndex[i]);\n          }\n        };\n      }else{\n        xAxisPoints.forEach(function(item, index) {\n          if (currentPoints.x + offset + spacing > item) {\n            current.index = index;\n          }\n        });\n      }\n    }\n  }\n  return current;\n}\n\nfunction findBarChartCurrentIndex(currentPoints, calPoints, opts, config) {\n  var offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;\n  var current={ index:-1, group:[] };\n  var spacing = opts.chartData.eachSpacing / 2;\n  let yAxisPoints = opts.chartData.yAxisPoints;\n  if (calPoints && calPoints.length > 0) {\n    if (isInExactChartArea(currentPoints, opts, config)) {\n      yAxisPoints.forEach(function(item, index) {\n        if (currentPoints.y + offset + spacing > item) {\n          current.index = index;\n        }\n      });\n    }\n  }\n  return current;\n}\n\nfunction findLegendIndex(currentPoints, legendData, opts) {\n  let currentIndex = -1;\n  let gap = 0;\n  if (isInExactLegendArea(currentPoints, legendData.area)) {\n    let points = legendData.points;\n    let index = -1;\n    for (let i = 0, len = points.length; i < len; i++) {\n      let item = points[i];\n      for (let j = 0; j < item.length; j++) {\n        index += 1;\n        let area = item[j]['area'];\n        if (area && currentPoints.x > area[0] - gap && currentPoints.x < area[2] + gap && currentPoints.y > area[1] - gap && currentPoints.y < area[3] + gap) {\n          currentIndex = index;\n          break;\n        }\n      }\n    }\n    return currentIndex;\n  }\n  return currentIndex;\n}\n\nfunction isInExactLegendArea(currentPoints, area) {\n  return currentPoints.x > area.start.x && currentPoints.x < area.end.x && currentPoints.y > area.start.y && currentPoints.y < area.end.y;\n}\n\nfunction isInExactChartArea(currentPoints, opts, config) {\n  return currentPoints.x <= opts.width - opts.area[1] + 10 && currentPoints.x >= opts.area[3] - 10 && currentPoints.y >= opts.area[0] && currentPoints.y <= opts.height - opts.area[2];\n}\n\nfunction findRadarChartCurrentIndex(currentPoints, radarData, count) {\n  var eachAngleArea = 2 * Math.PI / count;\n  var currentIndex = -1;\n  if (isInExactPieChartArea(currentPoints, radarData.center, radarData.radius)) {\n    var fixAngle = function fixAngle(angle) {\n      if (angle < 0) {\n        angle += 2 * Math.PI;\n      }\n      if (angle > 2 * Math.PI) {\n        angle -= 2 * Math.PI;\n      }\n      return angle;\n    };\n    var angle = Math.atan2(radarData.center.y - currentPoints.y, currentPoints.x - radarData.center.x);\n    angle = -1 * angle;\n    if (angle < 0) {\n      angle += 2 * Math.PI;\n    }\n    var angleList = radarData.angleList.map(function(item) {\n      item = fixAngle(-1 * item);\n      return item;\n    });\n    angleList.forEach(function(item, index) {\n      var rangeStart = fixAngle(item - eachAngleArea / 2);\n      var rangeEnd = fixAngle(item + eachAngleArea / 2);\n      if (rangeEnd < rangeStart) {\n        rangeEnd += 2 * Math.PI;\n      }\n      if (angle >= rangeStart && angle <= rangeEnd || angle + 2 * Math.PI >= rangeStart && angle + 2 * Math.PI <= rangeEnd) {\n        currentIndex = index;\n      }\n    });\n  }\n  return currentIndex;\n}\n\nfunction findFunnelChartCurrentIndex(currentPoints, funnelData) {\n  var currentIndex = -1;\n  for (var i = 0, len = funnelData.series.length; i < len; i++) {\n    var item = funnelData.series[i];\n    if (currentPoints.x > item.funnelArea[0] && currentPoints.x < item.funnelArea[2] && currentPoints.y > item.funnelArea[1] && currentPoints.y < item.funnelArea[3]) {\n      currentIndex = i;\n      break;\n    }\n  }\n  return currentIndex;\n}\n\nfunction findWordChartCurrentIndex(currentPoints, wordData) {\n  var currentIndex = -1;\n  for (var i = 0, len = wordData.length; i < len; i++) {\n    var item = wordData[i];\n    if (currentPoints.x > item.area[0] && currentPoints.x < item.area[2] && currentPoints.y > item.area[1] && currentPoints.y < item.area[3]) {\n      currentIndex = i;\n      break;\n    }\n  }\n  return currentIndex;\n}\n\nfunction findMapChartCurrentIndex(currentPoints, opts) {\n  var currentIndex = -1;\n  var cData = opts.chartData.mapData;\n  var data = opts.series;\n  var tmp = pointToCoordinate(currentPoints.y, currentPoints.x, cData.bounds, cData.scale, cData.xoffset, cData.yoffset);\n  var poi = [tmp.x, tmp.y];\n  for (var i = 0, len = data.length; i < len; i++) {\n    var item = data[i].geometry.coordinates;\n    if (isPoiWithinPoly(poi, item, opts.chartData.mapData.mercator)) {\n      currentIndex = i;\n      break;\n    }\n  }\n  return currentIndex;\n}\n\nfunction findRoseChartCurrentIndex(currentPoints, pieData, opts) {\n  var currentIndex = -1;\n  var series = getRoseDataPoints(opts._series_, opts.extra.rose.type, pieData.radius, pieData.radius);\n  if (pieData && pieData.center && isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)) {\n    var angle = Math.atan2(pieData.center.y - currentPoints.y, currentPoints.x - pieData.center.x);\n    angle = -angle;\n    if(opts.extra.rose && opts.extra.rose.offsetAngle){\n      angle = angle - opts.extra.rose.offsetAngle * Math.PI / 180;\n    }\n    for (var i = 0, len = series.length; i < len; i++) {\n      if (isInAngleRange(angle, series[i]._start_, series[i]._start_ + series[i]._rose_proportion_ * 2 * Math.PI)) {\n        currentIndex = i;\n        break;\n      }\n    }\n  }\n  return currentIndex;\n}\n\nfunction findPieChartCurrentIndex(currentPoints, pieData, opts) {\n  var currentIndex = -1;\n  var series = getPieDataPoints(pieData.series);\n  if (pieData && pieData.center && isInExactPieChartArea(currentPoints, pieData.center, pieData.radius)) {\n    var angle = Math.atan2(pieData.center.y - currentPoints.y, currentPoints.x - pieData.center.x);\n    angle = -angle;\n    if(opts.extra.pie && opts.extra.pie.offsetAngle){\n      angle = angle - opts.extra.pie.offsetAngle * Math.PI / 180;\n    }\n    if(opts.extra.ring && opts.extra.ring.offsetAngle){\n      angle = angle - opts.extra.ring.offsetAngle * Math.PI / 180;\n    }\n    for (var i = 0, len = series.length; i < len; i++) {\n      if (isInAngleRange(angle, series[i]._start_, series[i]._start_ + series[i]._proportion_ * 2 * Math.PI)) {\n        currentIndex = i;\n        break;\n      }\n    }\n  }\n  return currentIndex;\n}\n\nfunction isInExactPieChartArea(currentPoints, center, radius) {\n  return Math.pow(currentPoints.x - center.x, 2) + Math.pow(currentPoints.y - center.y, 2) <= Math.pow(radius, 2);\n}\n\n\nfunction splitPoints(points,eachSeries) {\n  var newPoints = [];\n  var items = [];\n  points.forEach(function(item, index) {\n    if(eachSeries.connectNulls){\n      if (item !== null) {\n        items.push(item);\n      }\n    }else{\n      if (item !== null) {\n        items.push(item);\n      } else {\n        if (items.length) {\n          newPoints.push(items);\n        }\n        items = [];\n      }\n    }\n    \n  });\n  if (items.length) {\n    newPoints.push(items);\n  }\n  return newPoints;\n}\n\n\nfunction calLegendData(series, opts, config, chartData, context) {\n  let legendData = {\n    area: {\n      start: {\n        x: 0,\n        y: 0\n      },\n      end: {\n        x: 0,\n        y: 0\n      },\n      width: 0,\n      height: 0,\n      wholeWidth: 0,\n      wholeHeight: 0\n    },\n    points: [],\n    widthArr: [],\n    heightArr: []\n  };\n  if (opts.legend.show === false) {\n    chartData.legendData = legendData;\n    return legendData;\n  }\n  let padding = opts.legend.padding * opts.pix;\n  let margin = opts.legend.margin * opts.pix;\n  let fontSize = opts.legend.fontSize ? opts.legend.fontSize * opts.pix : config.fontSize;\n  let shapeWidth = 15 * opts.pix;\n  let shapeRight = 5 * opts.pix;\n  let lineHeight = Math.max(opts.legend.lineHeight * opts.pix, fontSize);\n  if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {\n    let legendList = [];\n    let widthCount = 0;\n    let widthCountArr = [];\n    let currentRow = [];\n    for (let i = 0; i < series.length; i++) {\n      let item = series[i];\n      const legendText = item.legendText ? item.legendText : item.name;\n      let itemWidth = shapeWidth + shapeRight + measureText(legendText || 'undefined', fontSize, context) + opts.legend.itemGap * opts.pix;\n      if (widthCount + itemWidth > opts.width - opts.area[1] - opts.area[3]) {\n        legendList.push(currentRow);\n        widthCountArr.push(widthCount - opts.legend.itemGap * opts.pix);\n        widthCount = itemWidth;\n        currentRow = [item];\n      } else {\n        widthCount += itemWidth;\n        currentRow.push(item);\n      }\n    }\n    if (currentRow.length) {\n      legendList.push(currentRow);\n      widthCountArr.push(widthCount - opts.legend.itemGap * opts.pix);\n      legendData.widthArr = widthCountArr;\n      let legendWidth = Math.max.apply(null, widthCountArr);\n      switch (opts.legend.float) {\n        case 'left':\n          legendData.area.start.x = opts.area[3];\n          legendData.area.end.x = opts.area[3] + legendWidth + 2 * padding;\n          break;\n        case 'right':\n          legendData.area.start.x = opts.width - opts.area[1] - legendWidth - 2 * padding;\n          legendData.area.end.x = opts.width - opts.area[1];\n          break;\n        default:\n          legendData.area.start.x = (opts.width - legendWidth) / 2 - padding;\n          legendData.area.end.x = (opts.width + legendWidth) / 2 + padding;\n      }\n      legendData.area.width = legendWidth + 2 * padding;\n      legendData.area.wholeWidth = legendWidth + 2 * padding;\n      legendData.area.height = legendList.length * lineHeight + 2 * padding;\n      legendData.area.wholeHeight = legendList.length * lineHeight + 2 * padding + 2 * margin;\n      legendData.points = legendList;\n    }\n  } else {\n    let len = series.length;\n    let maxHeight = opts.height - opts.area[0] - opts.area[2] - 2 * margin - 2 * padding;\n    let maxLength = Math.min(Math.floor(maxHeight / lineHeight), len);\n    legendData.area.height = maxLength * lineHeight + padding * 2;\n    legendData.area.wholeHeight = maxLength * lineHeight + padding * 2;\n    switch (opts.legend.float) {\n      case 'top':\n        legendData.area.start.y = opts.area[0] + margin;\n        legendData.area.end.y = opts.area[0] + margin + legendData.area.height;\n        break;\n      case 'bottom':\n        legendData.area.start.y = opts.height - opts.area[2] - margin - legendData.area.height;\n        legendData.area.end.y = opts.height - opts.area[2] - margin;\n        break;\n      default:\n        legendData.area.start.y = (opts.height - legendData.area.height) / 2;\n        legendData.area.end.y = (opts.height + legendData.area.height) / 2;\n    }\n    let lineNum = len % maxLength === 0 ? len / maxLength : Math.floor((len / maxLength) + 1);\n    let currentRow = [];\n    for (let i = 0; i < lineNum; i++) {\n      let temp = series.slice(i * maxLength, i * maxLength + maxLength);\n      currentRow.push(temp);\n    }\n    legendData.points = currentRow;\n    if (currentRow.length) {\n      for (let i = 0; i < currentRow.length; i++) {\n        let item = currentRow[i];\n        let maxWidth = 0;\n        for (let j = 0; j < item.length; j++) {\n          let itemWidth = shapeWidth + shapeRight + measureText(item[j].name || 'undefined', fontSize, context) + opts.legend.itemGap * opts.pix;\n          if (itemWidth > maxWidth) {\n            maxWidth = itemWidth;\n          }\n        }\n        legendData.widthArr.push(maxWidth);\n        legendData.heightArr.push(item.length * lineHeight + padding * 2);\n      }\n      let legendWidth = 0\n      for (let i = 0; i < legendData.widthArr.length; i++) {\n        legendWidth += legendData.widthArr[i];\n      }\n      legendData.area.width = legendWidth - opts.legend.itemGap * opts.pix + 2 * padding;\n      legendData.area.wholeWidth = legendData.area.width + padding;\n    }\n  }\n  switch (opts.legend.position) {\n    case 'top':\n      legendData.area.start.y = opts.area[0] + margin;\n      legendData.area.end.y = opts.area[0] + margin + legendData.area.height;\n      break;\n    case 'bottom':\n      legendData.area.start.y = opts.height - opts.area[2] - legendData.area.height - margin;\n      legendData.area.end.y = opts.height - opts.area[2] - margin;\n      break;\n    case 'left':\n      legendData.area.start.x = opts.area[3];\n      legendData.area.end.x = opts.area[3] + legendData.area.width;\n      break;\n    case 'right':\n      legendData.area.start.x = opts.width - opts.area[1] - legendData.area.width;\n      legendData.area.end.x = opts.width - opts.area[1];\n      break;\n  }\n  chartData.legendData = legendData;\n  return legendData;\n}\n\nfunction calCategoriesData(categories, opts, config, eachSpacing, context) {\n  var result = {\n    angle: 0,\n    xAxisHeight: config.xAxisHeight\n  };\n  var fontSize = opts.xAxis.fontSize * opts.pix || config.fontSize;\n  var categoriesTextLenth = categories.map(function(item,index) {\n    var xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item,index,opts) : item;\n    return measureText(String(xitem), fontSize, context);\n  });\n  \n  var maxTextLength = Math.max.apply(this, categoriesTextLenth);\n  if (opts.xAxis.rotateLabel == true) {\n    result.angle = opts.xAxis.rotateAngle * Math.PI / 180;\n    let tempHeight = 2 * config.xAxisTextPadding +  Math.abs(maxTextLength * Math.sin(result.angle))\n    tempHeight = tempHeight < fontSize + 2 * config.xAxisTextPadding ? tempHeight + 2 * config.xAxisTextPadding : tempHeight;\n    if(opts.enableScroll == true && opts.xAxis.scrollShow == true){\n      tempHeight += 12 * opts.pix;\n    }\n    result.xAxisHeight = tempHeight;\n  }\n  if (opts.xAxis.disabled){\n    result.xAxisHeight = 0;\n  }\n  return result;\n}\n\nfunction getXAxisTextList(series, opts, config, stack) {\n  var index = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : -1;\n  var data;\n  if (stack == 'stack') {\n    data = dataCombineStack(series, opts.categories.length);\n  } else {\n    data = dataCombine(series);\n  }\n  var sorted = [];\n  // remove null from data\n  data = data.filter(function(item) {\n    //return item !== null;\n    if (typeof item === 'object' && item !== null) {\n      if (item.constructor.toString().indexOf('Array') > -1) {\n        return item !== null;\n      } else {\n        return item.value !== null;\n      }\n    } else {\n      return item !== null;\n    }\n  });\n  data.map(function(item) {\n    if (typeof item === 'object') {\n      if (item.constructor.toString().indexOf('Array') > -1) {\n        if (opts.type == 'candle') {\n          item.map(function(subitem) {\n            sorted.push(subitem);\n          })\n        } else {\n          sorted.push(item[0]);\n        }\n      } else {\n        sorted.push(item.value);\n      }\n    } else {\n      sorted.push(item);\n    }\n  })\n\n  var minData = 0;\n  var maxData = 0;\n  if (sorted.length > 0) {\n    minData = Math.min.apply(this, sorted);\n    maxData = Math.max.apply(this, sorted);\n  }\n  //为了兼容v1.9.0之前的项目\n  if (index > -1) {\n    if (typeof opts.xAxis.data[index].min === 'number') {\n      minData = Math.min(opts.xAxis.data[index].min, minData);\n    }\n    if (typeof opts.xAxis.data[index].max === 'number') {\n      maxData = Math.max(opts.xAxis.data[index].max, maxData);\n    }\n  } else {\n    if (typeof opts.xAxis.min === 'number') {\n      minData = Math.min(opts.xAxis.min, minData);\n    }\n    if (typeof opts.xAxis.max === 'number') {\n      maxData = Math.max(opts.xAxis.max, maxData);\n    }\n  }\n  if (minData === maxData) {\n    var rangeSpan = maxData || 10;\n    maxData += rangeSpan;\n  }\n  //var dataRange = getDataRange(minData, maxData);\n  var minRange = minData;\n  var maxRange = maxData;\n  var range = [];\n  var eachRange = (maxRange - minRange) / opts.xAxis.splitNumber;\n  for (var i = 0; i <= opts.xAxis.splitNumber; i++) {\n    range.push(minRange + eachRange * i);\n  }\n  return range;\n}\n\nfunction calXAxisData(series, opts, config, context) {\n  //堆叠图重算Y轴\n  var columnstyle = assign({}, {\n    type: \"\"\n  }, opts.extra.bar);\n  var result = {\n    angle: 0,\n    xAxisHeight: config.xAxisHeight\n  };\n  result.ranges = getXAxisTextList(series, opts, config, columnstyle.type);\n  result.rangesFormat = result.ranges.map(function(item) {\n    //item = opts.xAxis.formatter ? opts.xAxis.formatter(item) : util.toFixed(item, 2);\n    item = util.toFixed(item, 2);\n    return item;\n  });\n  var xAxisScaleValues = result.ranges.map(function(item) {\n    // 如果刻度值是浮点数,则保留两位小数\n    item = util.toFixed(item, 2);\n    // 若有自定义格式则调用自定义的格式化函数\n    //item = opts.xAxis.formatter ? opts.xAxis.formatter(Number(item)) : item;\n    return item;\n  });\n  result = Object.assign(result, getXAxisPoints(xAxisScaleValues, opts, config));\n  // 计算X轴刻度的属性譬如每个刻度的间隔,刻度的起始点\\结束点以及总长\n  var eachSpacing = result.eachSpacing;\n  var textLength = xAxisScaleValues.map(function(item) {\n    return measureText(item, opts.xAxis.fontSize * opts.pix || config.fontSize, context);\n  });\n  // get max length of categories text\n  var maxTextLength = Math.max.apply(this, textLength);\n  // 如果刻度值文本内容过长,则将其逆时针旋转45°\n  if (maxTextLength + 2 * config.xAxisTextPadding > eachSpacing) {\n    result.angle = 45 * Math.PI / 180;\n    result.xAxisHeight = 2 * config.xAxisTextPadding + maxTextLength * Math.sin(result.angle);\n  }\n  if (opts.xAxis.disabled === true) {\n    result.xAxisHeight = 0;\n  }\n  return result;\n}\n\nfunction getRadarDataPoints(angleList, center, radius, series, opts) {\n  var process = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;\n  var radarOption = opts.extra.radar || {};\n  radarOption.max = radarOption.max || 0;\n  var maxData = Math.max(radarOption.max, Math.max.apply(null, dataCombine(series)));\n  var data = [];\n  for (let i = 0; i < series.length; i++) {\n    let each = series[i];\n    let listItem = {};\n    listItem.color = each.color;\n    listItem.legendShape = each.legendShape;\n    listItem.pointShape = each.pointShape;\n    listItem.data = [];\n    each.data.forEach(function(item, index) {\n      let tmp = {};\n      tmp.angle = angleList[index];\n      tmp.proportion = item / maxData;\n      tmp.value = item;\n      tmp.position = convertCoordinateOrigin(radius * tmp.proportion * process * Math.cos(tmp.angle), radius * tmp.proportion * process * Math.sin(tmp.angle), center);\n      listItem.data.push(tmp);\n    });\n    data.push(listItem);\n  }\n  return data;\n}\n\nfunction getPieDataPoints(series, radius) {\n  var process = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;\n  var count = 0;\n  var _start_ = 0;\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item.data = item.data === null ? 0 : item.data;\n    count += item.data;\n  }\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item.data = item.data === null ? 0 : item.data;\n    if (count === 0) {\n      item._proportion_ = 1 / series.length * process;\n    } else {\n      item._proportion_ = item.data / count * process;\n    }\n    item._radius_ = radius;\n  }\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item._start_ = _start_;\n    _start_ += 2 * item._proportion_ * Math.PI;\n  }\n  return series;\n}\n\nfunction getFunnelDataPoints(series, radius, type, eachSpacing) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  series = series.sort(function(a, b) {\n    return parseInt(b.data) - parseInt(a.data);\n  });\n  for (let i = 0; i < series.length; i++) {\n    if(type == 'funnel'){\n      series[i].radius = series[i].data / series[0].data * radius * process;\n    }else{\n      series[i].radius =  (eachSpacing * (series.length - i)) / (eachSpacing * series.length) * radius * process;\n    }\n    series[i]._proportion_ = series[i].data / series[0].data;\n  }\n  if(type !== 'pyramid'){\n    series.reverse();\n  }\n  return series;\n}\n\nfunction getRoseDataPoints(series, type, minRadius, radius) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var count = 0;\n  var _start_ = 0;\n  var dataArr = [];\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item.data = item.data === null ? 0 : item.data;\n    count += item.data;\n    dataArr.push(item.data);\n  }\n  var minData = Math.min.apply(null, dataArr);\n  var maxData = Math.max.apply(null, dataArr);\n  var radiusLength = radius - minRadius;\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item.data = item.data === null ? 0 : item.data;\n    if (count === 0) {\n      item._proportion_ = 1 / series.length * process;\n      item._rose_proportion_ = 1 / series.length * process;\n    } else {\n      item._proportion_ = item.data / count * process;\n      if(type == 'area'){\n        item._rose_proportion_ = 1 / series.length * process;\n      }else{\n        item._rose_proportion_ = item.data / count * process;\n      }\n    }\n    item._radius_ = minRadius + radiusLength * ((item.data - minData) / (maxData - minData)) || radius;\n  }\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item._start_ = _start_;\n    _start_ += 2 * item._rose_proportion_ * Math.PI;\n  }\n  return series;\n}\n\nfunction getArcbarDataPoints(series, arcbarOption) {\n  var process = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;\n  if (process == 1) {\n    process = 0.999999;\n  }\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item.data = item.data === null ? 0 : item.data;\n    let totalAngle;\n    if (arcbarOption.type == 'circle') {\n      totalAngle = 2;\n    } else {\n      if (arcbarOption.endAngle < arcbarOption.startAngle) {\n        totalAngle = 2 + arcbarOption.endAngle - arcbarOption.startAngle;\n      } else {\n        totalAngle = arcbarOption.startAngle - arcbarOption.endAngle;\n      }\n    }\n    item._proportion_ = totalAngle * item.data * process + arcbarOption.startAngle;\n    if (item._proportion_ >= 2) {\n      item._proportion_ = item._proportion_ % 2;\n    }\n  }\n  return series;\n}\n\nfunction getGaugeArcbarDataPoints(series, arcbarOption) {\n  var process = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;\n  if (process == 1) {\n    process = 0.999999;\n  }\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item.data = item.data === null ? 0 : item.data;\n    let totalAngle;\n    if (arcbarOption.type == 'circle') {\n      totalAngle = 2;\n    } else {\n      if (arcbarOption.endAngle < arcbarOption.startAngle) {\n        totalAngle = 2 + arcbarOption.endAngle - arcbarOption.startAngle;\n      } else {\n        totalAngle = arcbarOption.startAngle - arcbarOption.endAngle;\n      }\n    }\n    item._proportion_ = totalAngle * item.data * process + arcbarOption.startAngle;\n    if (item._proportion_ >= 2) {\n      item._proportion_ = item._proportion_ % 2;\n    }\n  }\n  return series;\n}\n\nfunction getGaugeAxisPoints(categories, startAngle, endAngle) {\n  let totalAngle = startAngle - endAngle + 1;\n  let tempStartAngle = startAngle;\n  for (let i = 0; i < categories.length; i++) {\n    categories[i].value = categories[i].value === null ? 0 : categories[i].value;\n    categories[i]._startAngle_ = tempStartAngle;\n    categories[i]._endAngle_ = totalAngle * categories[i].value + startAngle;\n    if (categories[i]._endAngle_ >= 2) {\n      categories[i]._endAngle_ = categories[i]._endAngle_ % 2;\n    }\n    tempStartAngle = categories[i]._endAngle_;\n  }\n  return categories;\n}\n\nfunction getGaugeDataPoints(series, categories, gaugeOption) {\n  let process = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1;\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    item.data = item.data === null ? 0 : item.data;\n    if (gaugeOption.pointer.color == 'auto') {\n      for (let i = 0; i < categories.length; i++) {\n        if (item.data <= categories[i].value) {\n          item.color = categories[i].color;\n          break;\n        }\n      }\n    } else {\n      item.color = gaugeOption.pointer.color;\n    }\n    let totalAngle = gaugeOption.startAngle - gaugeOption.endAngle + 1;\n    item._endAngle_ = totalAngle * item.data + gaugeOption.startAngle;\n    item._oldAngle_ = gaugeOption.oldAngle;\n    if (gaugeOption.oldAngle < gaugeOption.endAngle) {\n      item._oldAngle_ += 2;\n    }\n    if (item.data >= gaugeOption.oldData) {\n      item._proportion_ = (item._endAngle_ - item._oldAngle_) * process + gaugeOption.oldAngle;\n    } else {\n      item._proportion_ = item._oldAngle_ - (item._oldAngle_ - item._endAngle_) * process;\n    }\n    if (item._proportion_ >= 2) {\n      item._proportion_ = item._proportion_ % 2;\n    }\n  }\n  return series;\n}\n\nfunction getPieTextMaxLength(series, config, context, opts) {\n  series = getPieDataPoints(series);\n  let maxLength = 0;\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    let text = item.formatter ? item.formatter(+item._proportion_.toFixed(2)) : util.toFixed(item._proportion_ * 100) + '%';\n    maxLength = Math.max(maxLength, measureText(text, item.textSize * opts.pix || config.fontSize, context));\n  }\n  return maxLength;\n}\n\nfunction fixColumeData(points, eachSpacing, columnLen, index, config, opts) {\n  return points.map(function(item) {\n    if (item === null) {\n      return null;\n    }\n    var seriesGap = 0;\n    var categoryGap = 0;\n    if (opts.type == 'mix') {\n      seriesGap = opts.extra.mix.column.seriesGap * opts.pix || 0;\n      categoryGap = opts.extra.mix.column.categoryGap * opts.pix || 0;\n    } else {\n      seriesGap = opts.extra.column.seriesGap * opts.pix || 0;\n      categoryGap = opts.extra.column.categoryGap * opts.pix || 0;\n    }\n    seriesGap =  Math.min(seriesGap, eachSpacing / columnLen)\n    categoryGap =  Math.min(categoryGap, eachSpacing / columnLen)\n    item.width = Math.ceil((eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) / columnLen);\n    if (opts.extra.mix && opts.extra.mix.column.width && +opts.extra.mix.column.width > 0) {\n      item.width = Math.min(item.width, +opts.extra.mix.column.width * opts.pix);\n    }\n    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {\n      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);\n    }\n    if (item.width <= 0) {\n      item.width = 1;\n    }\n    item.x += (index + 0.5 - columnLen / 2) * (item.width + seriesGap);\n    return item;\n  });\n}\n\nfunction fixBarData(points, eachSpacing, columnLen, index, config, opts) {\n  return points.map(function(item) {\n    if (item === null) {\n      return null;\n    }\n    var seriesGap = 0;\n    var categoryGap = 0;\n    seriesGap = opts.extra.bar.seriesGap * opts.pix || 0;\n    categoryGap = opts.extra.bar.categoryGap * opts.pix || 0;\n    seriesGap =  Math.min(seriesGap, eachSpacing / columnLen)\n    categoryGap =  Math.min(categoryGap, eachSpacing / columnLen)\n    item.width = Math.ceil((eachSpacing - 2 * categoryGap - seriesGap * (columnLen - 1)) / columnLen);\n    if (opts.extra.bar && opts.extra.bar.width && +opts.extra.bar.width > 0) {\n      item.width = Math.min(item.width, +opts.extra.bar.width * opts.pix);\n    }\n    if (item.width <= 0) {\n      item.width = 1;\n    }\n    item.y += (index + 0.5 - columnLen / 2) * (item.width + seriesGap);\n    return item;\n  });\n}\n\nfunction fixColumeMeterData(points, eachSpacing, columnLen, index, config, opts, border) {\n  var categoryGap = opts.extra.column.categoryGap * opts.pix || 0;\n  return points.map(function(item) {\n    if (item === null) {\n      return null;\n    }\n    item.width = eachSpacing - 2 * categoryGap;\n    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {\n      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);\n    }\n    if (index > 0) {\n      item.width -= border;\n    }\n    return item;\n  });\n}\n\nfunction fixColumeStackData(points, eachSpacing, columnLen, index, config, opts, series) {\n  var categoryGap = opts.extra.column.categoryGap * opts.pix || 0;\n  return points.map(function(item, indexn) {\n    if (item === null) {\n      return null;\n    }\n    item.width = Math.ceil(eachSpacing - 2 * categoryGap);\n    if (opts.extra.column && opts.extra.column.width && +opts.extra.column.width > 0) {\n      item.width = Math.min(item.width, +opts.extra.column.width * opts.pix);\n    }\n    if (item.width <= 0) {\n      item.width = 1;\n    }\n    return item;\n  });\n}\n\nfunction fixBarStackData(points, eachSpacing, columnLen, index, config, opts, series) {\n  var categoryGap = opts.extra.bar.categoryGap * opts.pix || 0;\n  return points.map(function(item, indexn) {\n    if (item === null) {\n      return null;\n    }\n    item.width = Math.ceil(eachSpacing - 2 * categoryGap);\n    if (opts.extra.bar && opts.extra.bar.width && +opts.extra.bar.width > 0) {\n      item.width = Math.min(item.width, +opts.extra.bar.width * opts.pix);\n    }\n    if (item.width <= 0) {\n      item.width = 1;\n    }\n    return item;\n  });\n}\n\nfunction getXAxisPoints(categories, opts, config) {\n  var spacingValid = opts.width - opts.area[1] - opts.area[3];\n  var dataCount = opts.enableScroll ? Math.min(opts.xAxis.itemCount, categories.length) : categories.length;\n  if ((opts.type == 'line' || opts.type == 'area' || opts.type == 'scatter' || opts.type == 'bubble' || opts.type == 'bar') && dataCount > 1 && opts.xAxis.boundaryGap == 'justify') {\n    dataCount -= 1;\n  }\n  var widthRatio = 0;\n  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){\n    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2\n    widthRatio = opts.extra.mount.widthRatio - 1;\n    dataCount += widthRatio;\n  }\n  var eachSpacing = spacingValid / dataCount;\n  var xAxisPoints = [];\n  var startX = opts.area[3];\n  var endX = opts.width - opts.area[1];\n  categories.forEach(function(item, index) {\n    xAxisPoints.push(startX + widthRatio / 2 * eachSpacing + index * eachSpacing);\n  });\n  if (opts.xAxis.boundaryGap !== 'justify') {\n    if (opts.enableScroll === true) {\n      xAxisPoints.push(startX + widthRatio * eachSpacing + categories.length * eachSpacing);\n    } else {\n      xAxisPoints.push(endX);\n    }\n  }\n  return {\n    xAxisPoints: xAxisPoints,\n    startX: startX,\n    endX: endX,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction getCandleDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config) {\n  var process = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1;\n  var points = [];\n  var validHeight = opts.height - opts.area[0] - opts.area[2];\n  data.forEach(function(item, index) {\n    if (item === null) {\n      points.push(null);\n    } else {\n      var cPoints = [];\n      item.forEach(function(items, indexs) {\n        var point = {};\n        point.x = xAxisPoints[index] + Math.round(eachSpacing / 2);\n        var value = items.value || items;\n        var height = validHeight * (value - minRange) / (maxRange - minRange);\n        height *= process;\n        point.y = opts.height - Math.round(height) - opts.area[2];\n        cPoints.push(point);\n      });\n      points.push(cPoints);\n    }\n  });\n  return points;\n}\n\nfunction getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config) {\n  var process = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1;\n  var boundaryGap = 'center';\n  if (opts.type == 'line' || opts.type == 'area' || opts.type == 'scatter' || opts.type == 'bubble' ) {\n    boundaryGap = opts.xAxis.boundaryGap;\n  }\n  var points = [];\n  var validHeight = opts.height - opts.area[0] - opts.area[2];\n  var validWidth = opts.width - opts.area[1] - opts.area[3];\n  data.forEach(function(item, index) {\n    if (item === null) {\n      points.push(null);\n    } else {\n      var point = {};\n      point.color = item.color;\n      point.x = xAxisPoints[index];\n      var value = item;\n      if (typeof item === 'object' && item !== null) {\n        if (item.constructor.toString().indexOf('Array') > -1) {\n          let xranges, xminRange, xmaxRange;\n          xranges = [].concat(opts.chartData.xAxisData.ranges);\n          xminRange = xranges.shift();\n          xmaxRange = xranges.pop();\n          value = item[1];\n          point.x = opts.area[3] + validWidth * (item[0] - xminRange) / (xmaxRange - xminRange);\n          if(opts.type == 'bubble'){\n            point.r = item[2];\n            point.t = item[3];\n          }\n        } else {\n          value = item.value;\n        }\n      }\n      if (boundaryGap == 'center') {\n        point.x += eachSpacing / 2;\n      }\n      var height = validHeight * (value - minRange) / (maxRange - minRange);\n      height *= process;\n      point.y = opts.height - height - opts.area[2];\n      points.push(point);\n    }\n  });\n  return points;\n}\n\nfunction getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption) {\n  var process = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1;\n  var points = [];\n  var validHeight = opts.height - opts.area[0] - opts.area[2];\n  var validWidth = opts.width - opts.area[1] - opts.area[3];\n  var mountWidth = eachSpacing * mountOption.widthRatio;\n  series.forEach(function(item, index) {\n    if (item === null) {\n      points.push(null);\n    } else {\n      var point = {};\n      point.color = item.color;\n      point.x = xAxisPoints[index];\n      point.x += eachSpacing / 2;\n      var value = item.data;\n      var height = validHeight * (value - minRange) / (maxRange - minRange);\n      height *= process;\n      point.y = opts.height - height - opts.area[2];\n      point.value = value;\n      point.width = mountWidth;\n      points.push(point);\n    }\n  });\n  return points;\n}\n\nfunction getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config) {\n  var process = arguments.length > 7 && arguments[7] !== undefined ? arguments[7] : 1;\n  var points = [];\n  var validHeight = opts.height - opts.area[0] - opts.area[2];\n  var validWidth = opts.width - opts.area[1] - opts.area[3];\n  data.forEach(function(item, index) {\n    if (item === null) {\n      points.push(null);\n    } else {\n      var point = {};\n      point.color = item.color;\n      point.y = yAxisPoints[index];\n      var value = item;\n      if (typeof item === 'object' && item !== null) {\n        value = item.value;\n      }\n      var height = validWidth * (value - minRange) / (maxRange - minRange);\n      height *= process;\n      point.height = height;\n      point.value = value;\n      point.x = height + opts.area[3];\n      points.push(point);\n    }\n  });\n  return points;\n}\n\nfunction getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, stackSeries) {\n  var process = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : 1;\n  var points = [];\n  var validHeight = opts.height - opts.area[0] - opts.area[2];\n  data.forEach(function(item, index) {\n    if (item === null) {\n      points.push(null);\n    } else {\n      var point = {};\n      point.color = item.color;\n      point.x = xAxisPoints[index] + Math.round(eachSpacing / 2);\n\n      if (seriesIndex > 0) {\n        var value = 0;\n        for (let i = 0; i <= seriesIndex; i++) {\n          value += stackSeries[i].data[index];\n        }\n        var value0 = value - item;\n        var height = validHeight * (value - minRange) / (maxRange - minRange);\n        var height0 = validHeight * (value0 - minRange) / (maxRange - minRange);\n      } else {\n        var value = item;\n        var height = validHeight * (value - minRange) / (maxRange - minRange);\n        var height0 = 0;\n      }\n      var heightc = height0;\n      height *= process;\n      heightc *= process;\n      point.y = opts.height - Math.round(height) - opts.area[2];\n      point.y0 = opts.height - Math.round(heightc) - opts.area[2];\n      points.push(point);\n    }\n  });\n  return points;\n}\n\nfunction getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, stackSeries) {\n  var process = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : 1;\n  var points = [];\n  var validHeight = opts.width - opts.area[1] - opts.area[3];\n  data.forEach(function(item, index) {\n    if (item === null) {\n      points.push(null);\n    } else {\n      var point = {};\n      point.color = item.color;\n      point.y = yAxisPoints[index];\n      if (seriesIndex > 0) {\n        var value = 0;\n        for (let i = 0; i <= seriesIndex; i++) {\n          value += stackSeries[i].data[index];\n        }\n        var value0 = value - item;\n        var height = validHeight * (value - minRange) / (maxRange - minRange);\n        var height0 = validHeight * (value0 - minRange) / (maxRange - minRange);\n      } else {\n        var value = item;\n        var height = validHeight * (value - minRange) / (maxRange - minRange);\n        var height0 = 0;\n      }\n      var heightc = height0;\n      height *= process;\n      heightc *= process;\n      point.height = height - heightc;\n      point.x = opts.area[3] + height;\n      point.x0 = opts.area[3] + heightc;\n      points.push(point);\n    }\n  });\n  return points;\n}\n\nfunction getYAxisTextList(series, opts, config, stack, yData) {\n  var index = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : -1;\n  var data;\n  if (stack == 'stack') {\n    data = dataCombineStack(series, opts.categories.length);\n  } else {\n    data = dataCombine(series);\n  }\n  var sorted = [];\n  // remove null from data\n  data = data.filter(function(item) {\n    //return item !== null;\n    if (typeof item === 'object' && item !== null) {\n      if (item.constructor.toString().indexOf('Array') > -1) {\n        return item !== null;\n      } else {\n        return item.value !== null;\n      }\n    } else {\n      return item !== null;\n    }\n  });\n  data.map(function(item) {\n    if (typeof item === 'object') {\n      if (item.constructor.toString().indexOf('Array') > -1) {\n        if (opts.type == 'candle') {\n          item.map(function(subitem) {\n            sorted.push(subitem);\n          })\n        } else {\n          sorted.push(item[1]);\n        }\n      } else {\n        sorted.push(item.value);\n      }\n    } else {\n      sorted.push(item);\n    }\n  })\n  var minData = yData.min || 0;\n  var maxData = yData.max || 0;\n  if (sorted.length > 0) {\n    minData = Math.min.apply(this, sorted);\n    maxData = Math.max.apply(this, sorted);\n  }\n  if (minData === maxData) {\n    // var rangeSpan = maxData || 10;\n    // maxData += rangeSpan;\n    if(maxData == 0){\n      maxData = 10;\n    }else{\n      minData = 0;\n    }\n  }\n  var dataRange = getDataRange(minData, maxData);\n  var minRange = (yData.min === undefined || yData.min === null) ? dataRange.minRange : yData.min;\n  var maxRange = (yData.max === undefined || yData.max === null) ? dataRange.maxRange : yData.max;\n  var range = [];\n  var eachRange = (maxRange - minRange) / opts.yAxis.splitNumber;\n  for (var i = 0; i <= opts.yAxis.splitNumber; i++) {\n    range.push(minRange + eachRange * i);\n  }\n  return range.reverse();\n}\n\nfunction calYAxisData(series, opts, config, context) {\n  //堆叠图重算Y轴\n  var columnstyle = assign({}, {\n    type: \"\"\n  }, opts.extra.column);\n  //如果是多Y轴，重新计算\n  var YLength = opts.yAxis.data.length;\n  var newSeries = new Array(YLength);\n  if (YLength > 0) {\n    for (let i = 0; i < YLength; i++) {\n      newSeries[i] = [];\n      for (let j = 0; j < series.length; j++) {\n        if (series[j].index == i) {\n          newSeries[i].push(series[j]);\n        }\n      }\n    }\n    var rangesArr = new Array(YLength);\n    var rangesFormatArr = new Array(YLength);\n    var yAxisWidthArr = new Array(YLength);\n\n    for (let i = 0; i < YLength; i++) {\n      let yData = opts.yAxis.data[i];\n      //如果总开关不显示，强制每个Y轴为不显示\n      if (opts.yAxis.disabled == true) {\n        yData.disabled = true;\n      }\n      if(yData.type === 'categories'){\n        if(!yData.formatter){\n          yData.formatter = (val,index,opts) => {return val + (yData.unit || '')};\n        }\n        yData.categories = yData.categories || opts.categories;\n        rangesArr[i] = yData.categories;\n      }else{\n        if(!yData.formatter){\n          yData.formatter = (val,index,opts) => {return val.toFixed(yData.tofix) + (yData.unit || '')};\n        }\n        rangesArr[i] = getYAxisTextList(newSeries[i], opts, config, columnstyle.type, yData, i);\n      }\n      let yAxisFontSizes = yData.fontSize * opts.pix || config.fontSize;\n      yAxisWidthArr[i] = {\n        position: yData.position ? yData.position : 'left',\n        width: 0\n      };\n      rangesFormatArr[i] = rangesArr[i].map(function(items,index) {\n        items = yData.formatter(items,index,opts);\n        yAxisWidthArr[i].width = Math.max(yAxisWidthArr[i].width, measureText(items, yAxisFontSizes, context) + 5);\n        return items;\n      });\n      let calibration = yData.calibration ? 4 * opts.pix : 0;\n      yAxisWidthArr[i].width += calibration + 3 * opts.pix;\n      if (yData.disabled === true) {\n        yAxisWidthArr[i].width = 0;\n      }\n    }\n  } else {\n    var rangesArr = new Array(1);\n    var rangesFormatArr = new Array(1);\n    var yAxisWidthArr = new Array(1);\n    if(opts.type === 'bar'){\n      rangesArr[0] = opts.categories;\n      if(!opts.yAxis.formatter){\n        opts.yAxis.formatter = (val,index,opts) => {return val + (opts.yAxis.unit || '')}\n      }\n    }else{\n      if(!opts.yAxis.formatter){\n        opts.yAxis.formatter = (val,index,opts) => {return val.toFixed(opts.yAxis.tofix ) + (opts.yAxis.unit || '')}\n      }\n      rangesArr[0] = getYAxisTextList(series, opts, config, columnstyle.type, {});\n    }\n    yAxisWidthArr[0] = {\n      position: 'left',\n      width: 0\n    };\n    var yAxisFontSize = opts.yAxis.fontSize * opts.pix || config.fontSize;\n    rangesFormatArr[0] = rangesArr[0].map(function(item,index) {\n      item = opts.yAxis.formatter(item,index,opts);\n      yAxisWidthArr[0].width = Math.max(yAxisWidthArr[0].width, measureText(item, yAxisFontSize, context) + 5);\n      return item;\n    });\n    yAxisWidthArr[0].width += 3 * opts.pix;\n    if (opts.yAxis.disabled === true) {\n      yAxisWidthArr[0] = {\n        position: 'left',\n        width: 0\n      };\n      opts.yAxis.data[0] = {\n        disabled: true\n      };\n    } else {\n      opts.yAxis.data[0] = {\n        disabled: false,\n        position: 'left',\n        max: opts.yAxis.max,\n        min: opts.yAxis.min,\n        formatter: opts.yAxis.formatter\n      };\n      if(opts.type === 'bar'){\n        opts.yAxis.data[0].categories = opts.categories;\n        opts.yAxis.data[0].type = 'categories';\n      }\n    }\n  }\n  return {\n    rangesFormat: rangesFormatArr,\n    ranges: rangesArr,\n    yAxisWidth: yAxisWidthArr\n  };\n}\n\nfunction calTooltipYAxisData(point, series, opts, config, eachSpacing) {\n  let ranges = [].concat(opts.chartData.yAxisData.ranges);\n  let spacingValid = opts.height - opts.area[0] - opts.area[2];\n  let minAxis = opts.area[0];\n  let items = [];\n  for (let i = 0; i < ranges.length; i++) {\n    let maxVal = ranges[i].shift();\n    let minVal = ranges[i].pop();\n    let item = maxVal - (maxVal - minVal) * (point - minAxis) / spacingValid;\n    item = opts.yAxis.data[i].formatter ? opts.yAxis.data[i].formatter(item) : item.toFixed(0);\n    items.push(String(item))\n  }\n  return items;\n}\n\nfunction calMarkLineData(points, opts) {\n  let minRange, maxRange;\n  let spacingValid = opts.height - opts.area[0] - opts.area[2];\n  for (let i = 0; i < points.length; i++) {\n    points[i].yAxisIndex = points[i].yAxisIndex ? points[i].yAxisIndex : 0;\n    let range = [].concat(opts.chartData.yAxisData.ranges[points[i].yAxisIndex]);\n    minRange = range.pop();\n    maxRange = range.shift();\n    let height = spacingValid * (points[i].value - minRange) / (maxRange - minRange);\n    points[i].y = opts.height - Math.round(height) - opts.area[2];\n  }\n  return points;\n}\n\nfunction contextRotate(context, opts) {\n  if (opts.rotateLock !== true) {\n    context.translate(opts.height, 0);\n    context.rotate(90 * Math.PI / 180);\n  } else if (opts._rotate_ !== true) {\n    context.translate(opts.height, 0);\n    context.rotate(90 * Math.PI / 180);\n    opts._rotate_ = true;\n  }\n}\n\nfunction drawPointShape(points, color, shape, context, opts) {\n  context.beginPath();\n  if (opts.dataPointShapeType == 'hollow') {\n    context.setStrokeStyle(color);\n    context.setFillStyle(opts.background);\n    context.setLineWidth(2 * opts.pix);\n  } else {\n    context.setStrokeStyle(\"#ffffff\");\n    context.setFillStyle(color);\n    context.setLineWidth(1 * opts.pix);\n  }\n  if (shape === 'diamond') {\n    points.forEach(function(item, index) {\n      if (item !== null) {\n        context.moveTo(item.x, item.y - 4.5);\n        context.lineTo(item.x - 4.5, item.y);\n        context.lineTo(item.x, item.y + 4.5);\n        context.lineTo(item.x + 4.5, item.y);\n        context.lineTo(item.x, item.y - 4.5);\n      }\n    });\n  } else if (shape === 'circle') {\n    points.forEach(function(item, index) {\n      if (item !== null) {\n        context.moveTo(item.x + 2.5 * opts.pix, item.y);\n        context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);\n      }\n    });\n  } else if (shape === 'square') {\n    points.forEach(function(item, index) {\n      if (item !== null) {\n        context.moveTo(item.x - 3.5, item.y - 3.5);\n        context.rect(item.x - 3.5, item.y - 3.5, 7, 7);\n      }\n    });\n  } else if (shape === 'triangle') {\n    points.forEach(function(item, index) {\n      if (item !== null) {\n        context.moveTo(item.x, item.y - 4.5);\n        context.lineTo(item.x - 4.5, item.y + 4.5);\n        context.lineTo(item.x + 4.5, item.y + 4.5);\n        context.lineTo(item.x, item.y - 4.5);\n      }\n    });\n  } else if (shape === 'triangle') {\n    return;\n  }\n  context.closePath();\n  context.fill();\n  context.stroke();\n}\n\nfunction drawRingTitle(opts, config, context, center) {\n  var titlefontSize = opts.title.fontSize || config.titleFontSize;\n  var subtitlefontSize = opts.subtitle.fontSize || config.subtitleFontSize;\n  var title = opts.title.name || '';\n  var subtitle = opts.subtitle.name || '';\n  var titleFontColor = opts.title.color || opts.fontColor;\n  var subtitleFontColor = opts.subtitle.color || opts.fontColor;\n  var titleHeight = title ? titlefontSize : 0;\n  var subtitleHeight = subtitle ? subtitlefontSize : 0;\n  var margin = 5;\n  if (subtitle) {\n    var textWidth = measureText(subtitle, subtitlefontSize * opts.pix, context);\n    var startX = center.x - textWidth / 2 + (opts.subtitle.offsetX|| 0) * opts.pix ;\n    var startY = center.y + subtitlefontSize * opts.pix / 2 + (opts.subtitle.offsetY || 0) * opts.pix;\n    if (title) {\n      startY += (titleHeight * opts.pix + margin) / 2;\n    }\n    context.beginPath();\n    context.setFontSize(subtitlefontSize * opts.pix);\n    context.setFillStyle(subtitleFontColor);\n    context.fillText(subtitle, startX, startY);\n    context.closePath();\n    context.stroke();\n  }\n  if (title) {\n    var _textWidth = measureText(title, titlefontSize * opts.pix, context);\n    var _startX = center.x - _textWidth / 2 + (opts.title.offsetX || 0);\n    var _startY = center.y + titlefontSize * opts.pix / 2 + (opts.title.offsetY || 0) * opts.pix;\n    if (subtitle) {\n      _startY -= (subtitleHeight * opts.pix + margin) / 2;\n    }\n    context.beginPath();\n    context.setFontSize(titlefontSize * opts.pix);\n    context.setFillStyle(titleFontColor);\n    context.fillText(title, _startX, _startY);\n    context.closePath();\n    context.stroke();\n  }\n}\n\nfunction drawPointText(points, series, config, context, opts) {\n  // 绘制数据文案\n  var data = series.data;\n  var textOffset = series.textOffset ? series.textOffset : 0;\n  points.forEach(function(item, index) {\n    if (item !== null) {\n      context.beginPath();\n      var fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;\n      context.setFontSize(fontSize);\n      context.setFillStyle(series.textColor || opts.fontColor);\n      var value = data[index]\n      if (typeof data[index] === 'object' && data[index] !== null) {\n        if (data[index].constructor.toString().indexOf('Array')>-1) {\n          value = data[index][1];\n        } else {\n          value = data[index].value\n        }\n      }\n      var formatVal = series.formatter ? series.formatter(value,index,series,opts) : value;\n      context.setTextAlign('center');\n      context.fillText(String(formatVal), item.x, item.y - 4 + textOffset * opts.pix);\n      context.closePath();\n      context.stroke();\n      context.setTextAlign('left');\n    }\n  });\n}\n\nfunction drawMountPointText(points, series, config, context, opts) {\n  // 绘制数据文案\n  var data = series.data;\n  var textOffset = series.textOffset ? series.textOffset : 0;\n  points.forEach(function(item, index) {\n    if (item !== null) {\n      context.beginPath();\n      var fontSize = series[index].textSize ? series[index].textSize * opts.pix : config.fontSize;\n      context.setFontSize(fontSize);\n      context.setFillStyle(series[index].textColor || opts.fontColor);\n      var value = item.value\n      var formatVal = series[index].formatter ? series[index].formatter(value,index,series,opts) : value;\n      context.setTextAlign('center');\n      context.fillText(String(formatVal), item.x, item.y - 4 + textOffset * opts.pix);\n      context.closePath();\n      context.stroke();\n      context.setTextAlign('left');\n    }\n  });\n}\n\nfunction drawBarPointText(points, series, config, context, opts) {\n  // 绘制数据文案\n  var data = series.data;\n  var textOffset = series.textOffset ? series.textOffset : 0;\n  points.forEach(function(item, index) {\n    if (item !== null) {\n      context.beginPath();\n      var fontSize = series.textSize ? series.textSize * opts.pix : config.fontSize;\n      context.setFontSize(fontSize);\n      context.setFillStyle(series.textColor || opts.fontColor);\n      var value = data[index]\n      if (typeof data[index] === 'object' && data[index] !== null) {\n        value = data[index].value ;\n      }\n      var formatVal = series.formatter ? series.formatter(value,index,series,opts) : value;\n      context.setTextAlign('left');\n      context.fillText(String(formatVal), item.x + 4 * opts.pix , item.y + fontSize / 2 - 3 );\n      context.closePath();\n      context.stroke();\n    }\n  });\n}\n\nfunction drawGaugeLabel(gaugeOption, radius, centerPosition, opts, config, context) {\n  radius -= gaugeOption.width / 2 + gaugeOption.labelOffset * opts.pix;\n  radius = radius < 10 ? 10 : radius;\n  let totalAngle = gaugeOption.startAngle - gaugeOption.endAngle + 1;\n  let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;\n  let totalNumber = gaugeOption.endNumber - gaugeOption.startNumber;\n  let splitNumber = totalNumber / gaugeOption.splitLine.splitNumber;\n  let nowAngle = gaugeOption.startAngle;\n  let nowNumber = gaugeOption.startNumber;\n  for (let i = 0; i < gaugeOption.splitLine.splitNumber + 1; i++) {\n    var pos = {\n      x: radius * Math.cos(nowAngle * Math.PI),\n      y: radius * Math.sin(nowAngle * Math.PI)\n    };\n    var labelText = gaugeOption.formatter ? gaugeOption.formatter(nowNumber,i,opts) : nowNumber;\n    pos.x += centerPosition.x - measureText(labelText, config.fontSize, context) / 2;\n    pos.y += centerPosition.y;\n    var startX = pos.x;\n    var startY = pos.y;\n    context.beginPath();\n    context.setFontSize(config.fontSize);\n    context.setFillStyle(gaugeOption.labelColor || opts.fontColor);\n    context.fillText(labelText, startX, startY + config.fontSize / 2);\n    context.closePath();\n    context.stroke();\n    nowAngle += splitAngle;\n    if (nowAngle >= 2) {\n      nowAngle = nowAngle % 2;\n    }\n    nowNumber += splitNumber;\n  }\n}\n\nfunction drawRadarLabel(angleList, radius, centerPosition, opts, config, context) {\n  var radarOption = opts.extra.radar || {};\n  angleList.forEach(function(angle, index) {\n    if(radarOption.labelPointShow === true && opts.categories[index] !== ''){\n      var posPoint = {\n        x: radius * Math.cos(angle),\n        y: radius * Math.sin(angle)\n      };\n      var posPointAxis = convertCoordinateOrigin(posPoint.x, posPoint.y, centerPosition);\n      context.setFillStyle(radarOption.labelPointColor);\n      context.beginPath();\n      context.arc(posPointAxis.x, posPointAxis.y, radarOption.labelPointRadius * opts.pix, 0, 2 * Math.PI, false);\n      context.closePath();\n      context.fill();\n    }\n    var pos = {\n      x: (radius + config.radarLabelTextMargin * opts.pix) * Math.cos(angle),\n      y: (radius + config.radarLabelTextMargin * opts.pix) * Math.sin(angle)\n    };\n    var posRelativeCanvas = convertCoordinateOrigin(pos.x, pos.y, centerPosition);\n    var startX = posRelativeCanvas.x;\n    var startY = posRelativeCanvas.y;\n    if (util.approximatelyEqual(pos.x, 0)) {\n      startX -= measureText(opts.categories[index] || '', config.fontSize, context) / 2;\n    } else if (pos.x < 0) {\n      startX -= measureText(opts.categories[index] || '', config.fontSize, context);\n    }\n    context.beginPath();\n    context.setFontSize(config.fontSize);\n    context.setFillStyle(radarOption.labelColor || opts.fontColor);\n    context.fillText(opts.categories[index] || '', startX, startY + config.fontSize / 2);\n    context.closePath();\n    context.stroke();\n  });\n\n}\n\nfunction drawPieText(series, opts, config, context, radius, center) {\n  var lineRadius = config.pieChartLinePadding;\n  var textObjectCollection = [];\n  var lastTextObject = null;\n  var seriesConvert = series.map(function(item,index) {\n    var text = item.formatter ? item.formatter(item,index,series,opts) : util.toFixed(item._proportion_.toFixed(4) * 100) + '%';\n    text = item.labelText ? item.labelText : text;\n    var arc = 2 * Math.PI - (item._start_ + 2 * Math.PI * item._proportion_ / 2);\n    if (item._rose_proportion_) {\n      arc = 2 * Math.PI - (item._start_ + 2 * Math.PI * item._rose_proportion_ / 2);\n    }\n    var color = item.color;\n    var radius = item._radius_;\n    return {\n      arc: arc,\n      text: text,\n      color: color,\n      radius: radius,\n      textColor: item.textColor,\n      textSize: item.textSize,\n      labelShow: item.labelShow\n    };\n  });\n  for (let i = 0; i < seriesConvert.length; i++) {\n    let item = seriesConvert[i];\n    // line end\n    let orginX1 = Math.cos(item.arc) * (item.radius + lineRadius);\n    let orginY1 = Math.sin(item.arc) * (item.radius + lineRadius);\n    // line start\n    let orginX2 = Math.cos(item.arc) * item.radius;\n    let orginY2 = Math.sin(item.arc) * item.radius;\n    // text start\n    let orginX3 = orginX1 >= 0 ? orginX1 + config.pieChartTextPadding : orginX1 - config.pieChartTextPadding;\n    let orginY3 = orginY1;\n    let textWidth = measureText(item.text, item.textSize * opts.pix || config.fontSize, context);\n    let startY = orginY3;\n    if (lastTextObject && util.isSameXCoordinateArea(lastTextObject.start, {\n        x: orginX3\n      })) {\n      if (orginX3 > 0) {\n        startY = Math.min(orginY3, lastTextObject.start.y);\n      } else if (orginX1 < 0) {\n        startY = Math.max(orginY3, lastTextObject.start.y);\n      } else {\n        if (orginY3 > 0) {\n          startY = Math.max(orginY3, lastTextObject.start.y);\n        } else {\n          startY = Math.min(orginY3, lastTextObject.start.y);\n        }\n      }\n    }\n    if (orginX3 < 0) {\n      orginX3 -= textWidth;\n    }\n    let textObject = {\n      lineStart: {\n        x: orginX2,\n        y: orginY2\n      },\n      lineEnd: {\n        x: orginX1,\n        y: orginY1\n      },\n      start: {\n        x: orginX3,\n        y: startY\n      },\n      width: textWidth,\n      height: config.fontSize,\n      text: item.text,\n      color: item.color,\n      textColor: item.textColor,\n      textSize: item.textSize\n    };\n    lastTextObject = avoidCollision(textObject, lastTextObject);\n    textObjectCollection.push(lastTextObject);\n  }\n  for (let i = 0; i < textObjectCollection.length; i++) {\n    if(seriesConvert[i].labelShow === false){\n      continue;\n    }\n    let item = textObjectCollection[i];\n    let lineStartPoistion = convertCoordinateOrigin(item.lineStart.x, item.lineStart.y, center);\n    let lineEndPoistion = convertCoordinateOrigin(item.lineEnd.x, item.lineEnd.y, center);\n    let textPosition = convertCoordinateOrigin(item.start.x, item.start.y, center);\n    context.setLineWidth(1 * opts.pix);\n    context.setFontSize(item.textSize * opts.pix || config.fontSize);\n    context.beginPath();\n    context.setStrokeStyle(item.color);\n    context.setFillStyle(item.color);\n    context.moveTo(lineStartPoistion.x, lineStartPoistion.y);\n    let curveStartX = item.start.x < 0 ? textPosition.x + item.width : textPosition.x;\n    let textStartX = item.start.x < 0 ? textPosition.x - 5 : textPosition.x + 5;\n    context.quadraticCurveTo(lineEndPoistion.x, lineEndPoistion.y, curveStartX, textPosition.y);\n    context.moveTo(lineStartPoistion.x, lineStartPoistion.y);\n    context.stroke();\n    context.closePath();\n    context.beginPath();\n    context.moveTo(textPosition.x + item.width, textPosition.y);\n    context.arc(curveStartX, textPosition.y, 2 * opts.pix, 0, 2 * Math.PI);\n    context.closePath();\n    context.fill();\n    context.beginPath();\n    context.setFontSize(item.textSize * opts.pix || config.fontSize);\n    context.setFillStyle(item.textColor || opts.fontColor);\n    context.fillText(item.text, textStartX, textPosition.y + 3);\n    context.closePath();\n    context.stroke();\n    context.closePath();\n  }\n}\n\nfunction drawToolTipSplitLine(offsetX, opts, config, context) {\n  var toolTipOption = opts.extra.tooltip || {};\n  toolTipOption.gridType = toolTipOption.gridType == undefined ? 'solid' : toolTipOption.gridType;\n  toolTipOption.dashLength = toolTipOption.dashLength == undefined ? 4 : toolTipOption.dashLength;\n  var startY = opts.area[0];\n  var endY = opts.height - opts.area[2];\n  if (toolTipOption.gridType == 'dash') {\n    context.setLineDash([toolTipOption.dashLength, toolTipOption.dashLength]);\n  }\n  context.setStrokeStyle(toolTipOption.gridColor || '#cccccc');\n  context.setLineWidth(1 * opts.pix);\n  context.beginPath();\n  context.moveTo(offsetX, startY);\n  context.lineTo(offsetX, endY);\n  context.stroke();\n  context.setLineDash([]);\n  if (toolTipOption.xAxisLabel) {\n    let labelText = opts.categories[opts.tooltip.index];\n    context.setFontSize(config.fontSize);\n    let textWidth = measureText(labelText, config.fontSize, context);\n    let textX = offsetX - 0.5 * textWidth;\n    let textY = endY;\n    context.beginPath();\n    context.setFillStyle(hexToRgb(toolTipOption.labelBgColor || config.toolTipBackground, toolTipOption.labelBgOpacity || config.toolTipOpacity));\n    context.setStrokeStyle(toolTipOption.labelBgColor || config.toolTipBackground);\n    context.setLineWidth(1 * opts.pix);\n    context.rect(textX - config.toolTipPadding, textY, textWidth + 2 * config.toolTipPadding, config.fontSize + 2 * config.toolTipPadding);\n    context.closePath();\n    context.stroke();\n    context.fill();\n    context.beginPath();\n    context.setFontSize(config.fontSize);\n    context.setFillStyle(toolTipOption.labelFontColor || opts.fontColor);\n    context.fillText(String(labelText), textX, textY + config.toolTipPadding + config.fontSize);\n    context.closePath();\n    context.stroke();\n  }\n}\n\nfunction drawMarkLine(opts, config, context) {\n  let markLineOption = assign({}, {\n    type: 'solid',\n    dashLength: 4,\n    data: []\n  }, opts.extra.markLine);\n  let startX = opts.area[3];\n  let endX = opts.width - opts.area[1];\n  let points = calMarkLineData(markLineOption.data, opts);\n  for (let i = 0; i < points.length; i++) {\n    let item = assign({}, {\n      lineColor: '#DE4A42',\n      showLabel: false,\n      labelFontColor: '#666666',\n      labelBgColor: '#DFE8FF',\n      labelBgOpacity: 0.8,\n      labelAlign: 'left',\n      labelOffsetX: 0,\n      labelOffsetY: 0,\n    }, points[i]);\n    if (markLineOption.type == 'dash') {\n      context.setLineDash([markLineOption.dashLength, markLineOption.dashLength]);\n    }\n    context.setStrokeStyle(item.lineColor);\n    context.setLineWidth(1 * opts.pix);\n    context.beginPath();\n    context.moveTo(startX, item.y);\n    context.lineTo(endX, item.y);\n    context.stroke();\n    context.setLineDash([]);\n    if (item.showLabel) {\n      let labelText = item.labelText ? item.labelText : item.value;\n      context.setFontSize(config.fontSize);\n      let textWidth = measureText(labelText, config.fontSize, context);\n      let bgWidth = textWidth + config.toolTipPadding * 2;\n      let bgStartX = item.labelAlign == 'left' ? opts.area[3] - bgWidth : opts.width - opts.area[1];\n      bgStartX += item.labelOffsetX;\n      let bgStartY = item.y - 0.5 * config.fontSize - config.toolTipPadding;\n      bgStartY += item.labelOffsetY;\n      let textX = bgStartX + config.toolTipPadding;\n      let textY = item.y;\n      context.setFillStyle(hexToRgb(item.labelBgColor, item.labelBgOpacity));\n      context.setStrokeStyle(item.labelBgColor);\n      context.setLineWidth(1 * opts.pix);\n      context.beginPath();\n      context.rect(bgStartX, bgStartY, bgWidth, config.fontSize + 2 * config.toolTipPadding);\n      context.closePath();\n      context.stroke();\n      context.fill();\n      context.setFontSize(config.fontSize);\n      context.setTextAlign('left');\n      context.setFillStyle(item.labelFontColor);\n      context.fillText(String(labelText), textX, bgStartY + config.fontSize + config.toolTipPadding/2);\n      context.stroke();\n      context.setTextAlign('left');\n    }\n  }\n}\n\nfunction drawToolTipHorizentalLine(opts, config, context, eachSpacing, xAxisPoints) {\n  var toolTipOption = assign({}, {\n    gridType: 'solid',\n    dashLength: 4\n  }, opts.extra.tooltip);\n  var startX = opts.area[3];\n  var endX = opts.width - opts.area[1];\n  if (toolTipOption.gridType == 'dash') {\n    context.setLineDash([toolTipOption.dashLength, toolTipOption.dashLength]);\n  }\n  context.setStrokeStyle(toolTipOption.gridColor || '#cccccc');\n  context.setLineWidth(1 * opts.pix);\n  context.beginPath();\n  context.moveTo(startX, opts.tooltip.offset.y);\n  context.lineTo(endX, opts.tooltip.offset.y);\n  context.stroke();\n  context.setLineDash([]);\n  if (toolTipOption.yAxisLabel) {\n    let labelText = calTooltipYAxisData(opts.tooltip.offset.y, opts.series, opts, config, eachSpacing);\n    let widthArr = opts.chartData.yAxisData.yAxisWidth;\n    let tStartLeft = opts.area[3];\n    let tStartRight = opts.width - opts.area[1];\n    for (let i = 0; i < labelText.length; i++) {\n      context.setFontSize(config.fontSize);\n      let textWidth = measureText(labelText[i], config.fontSize, context);\n      let bgStartX, bgEndX, bgWidth;\n      if (widthArr[i].position == 'left') {\n        bgStartX = tStartLeft - widthArr[i].width;\n        bgEndX = Math.max(bgStartX, bgStartX + textWidth + config.toolTipPadding * 2);\n      } else {\n        bgStartX = tStartRight;\n        bgEndX = Math.max(bgStartX + widthArr[i].width, bgStartX + textWidth + config.toolTipPadding * 2);\n      }\n      bgWidth = bgEndX - bgStartX;\n      let textX = bgStartX + (bgWidth - textWidth) / 2;\n      let textY = opts.tooltip.offset.y;\n      context.beginPath();\n      context.setFillStyle(hexToRgb(toolTipOption.labelBgColor || config.toolTipBackground, toolTipOption.labelBgOpacity || config.toolTipOpacity));\n      context.setStrokeStyle(toolTipOption.labelBgColor || config.toolTipBackground);\n      context.setLineWidth(1 * opts.pix);\n      context.rect(bgStartX, textY - 0.5 * config.fontSize - config.toolTipPadding, bgWidth, config.fontSize + 2 *\n        config.toolTipPadding);\n      context.closePath();\n      context.stroke();\n      context.fill();\n      context.beginPath();\n      context.setFontSize(config.fontSize);\n      context.setFillStyle(toolTipOption.labelFontColor || opts.fontColor);\n      context.fillText(labelText[i], textX, textY + 0.5 * config.fontSize);\n      context.closePath();\n      context.stroke();\n      if (widthArr[i].position == 'left') {\n        tStartLeft -= (widthArr[i].width + opts.yAxis.padding * opts.pix);\n      } else {\n        tStartRight += widthArr[i].width + opts.yAxis.padding * opts.pix;\n      }\n    }\n  }\n}\n\nfunction drawToolTipSplitArea(offsetX, opts, config, context, eachSpacing) {\n  var toolTipOption = assign({}, {\n    activeBgColor: '#000000',\n    activeBgOpacity: 0.08,\n    activeWidth: eachSpacing\n  }, opts.extra.column);\n  toolTipOption.activeWidth = toolTipOption.activeWidth > eachSpacing ? eachSpacing : toolTipOption.activeWidth;\n  var startY = opts.area[0];\n  var endY = opts.height - opts.area[2];\n  context.beginPath();\n  context.setFillStyle(hexToRgb(toolTipOption.activeBgColor, toolTipOption.activeBgOpacity));\n  context.rect(offsetX - toolTipOption.activeWidth / 2, startY, toolTipOption.activeWidth, endY - startY);\n  context.closePath();\n  context.fill();\n  context.setFillStyle(\"#FFFFFF\");\n}\n\nfunction drawBarToolTipSplitArea(offsetX, opts, config, context, eachSpacing) {\n  var toolTipOption = assign({}, {\n    activeBgColor: '#000000',\n    activeBgOpacity: 0.08\n  }, opts.extra.bar);\n  var startX = opts.area[3];\n  var endX = opts.width - opts.area[1];\n  context.beginPath();\n  context.setFillStyle(hexToRgb(toolTipOption.activeBgColor, toolTipOption.activeBgOpacity));\n  context.rect( startX ,offsetX - eachSpacing / 2 ,  endX - startX,eachSpacing);\n  context.closePath();\n  context.fill();\n  context.setFillStyle(\"#FFFFFF\");\n}\n\n\nfunction drawToolTip(textList, offset, opts, config, context, eachSpacing, xAxisPoints) {\n  var toolTipOption = assign({}, {\n    showBox: true,\n    showArrow: true,\n    showCategory: false,\n    bgColor: '#000000',\n    bgOpacity: 0.7,\n    borderColor: '#000000',\n    borderWidth: 0,\n    borderRadius: 0,\n    borderOpacity: 0.7,\n    fontColor: '#FFFFFF',\n    splitLine: true,\n  }, opts.extra.tooltip);\n  if(toolTipOption.showCategory==true && opts.categories){\n    textList.unshift({text:opts.categories[opts.tooltip.index],color:null})\n  }\n  var legendWidth = 4 * opts.pix;\n  var legendMarginRight = 5 * opts.pix;\n  var arrowWidth = toolTipOption.showArrow ? 8 * opts.pix : 0;\n  var isOverRightBorder = false;\n  if (opts.type == 'line' || opts.type == 'mount' || opts.type == 'area' || opts.type == 'candle' || opts.type == 'mix') {\n    if (toolTipOption.splitLine == true) {\n      drawToolTipSplitLine(opts.tooltip.offset.x, opts, config, context);\n    }\n  }\n  offset = assign({\n    x: 0,\n    y: 0\n  }, offset);\n  offset.y -= 8 * opts.pix;\n  var textWidth = textList.map(function(item) {\n    return measureText(item.text, config.fontSize, context);\n  });\n  var toolTipWidth = legendWidth + legendMarginRight + 4 * config.toolTipPadding + Math.max.apply(null, textWidth);\n  var toolTipHeight = 2 * config.toolTipPadding + textList.length * config.toolTipLineHeight;\n  if (toolTipOption.showBox == false) {\n    return\n  }\n  // if beyond the right border\n  if (offset.x - Math.abs(opts._scrollDistance_ || 0) + arrowWidth + toolTipWidth > opts.width) {\n    isOverRightBorder = true;\n  }\n  if (toolTipHeight + offset.y > opts.height) {\n    offset.y = opts.height - toolTipHeight;\n  }\n  // draw background rect\n  context.beginPath();\n  context.setFillStyle(hexToRgb(toolTipOption.bgColor || config.toolTipBackground, toolTipOption.bgOpacity || config.toolTipOpacity));\n  context.setLineWidth(toolTipOption.borderWidth * opts.pix);\n  context.setStrokeStyle(hexToRgb(toolTipOption.borderColor, toolTipOption.borderOpacity));\n  var radius = toolTipOption.borderRadius;\n  if (isOverRightBorder) {\n    if (toolTipOption.showArrow) {\n      context.moveTo(offset.x, offset.y + 10 * opts.pix);\n      context.lineTo(offset.x - arrowWidth, offset.y + 10 * opts.pix + 5 * opts.pix);\n    }\n    context.arc(offset.x - arrowWidth - radius, offset.y + toolTipHeight - radius, radius, 0, Math.PI / 2, false);\n    context.arc(offset.x - arrowWidth - Math.round(toolTipWidth) + radius, offset.y + toolTipHeight - radius, radius,\n      Math.PI / 2, Math.PI, false);\n    context.arc(offset.x - arrowWidth - Math.round(toolTipWidth) + radius, offset.y + radius, radius, -Math.PI, -Math.PI / 2, false);\n    context.arc(offset.x - arrowWidth - radius, offset.y + radius, radius, -Math.PI / 2, 0, false);\n    if (toolTipOption.showArrow) {\n      context.lineTo(offset.x - arrowWidth, offset.y + 10 * opts.pix - 5 * opts.pix);\n      context.lineTo(offset.x, offset.y + 10 * opts.pix);\n    }\n  } else {\n    if (toolTipOption.showArrow) {\n      context.moveTo(offset.x, offset.y + 10 * opts.pix);\n      context.lineTo(offset.x + arrowWidth, offset.y + 10 * opts.pix - 5 * opts.pix);\n    }\n    context.arc(offset.x + arrowWidth + radius, offset.y + radius, radius, -Math.PI, -Math.PI / 2, false);\n    context.arc(offset.x + arrowWidth + Math.round(toolTipWidth) - radius, offset.y + radius, radius, -Math.PI / 2, 0,\n      false);\n    context.arc(offset.x + arrowWidth + Math.round(toolTipWidth) - radius, offset.y + toolTipHeight - radius, radius, 0,\n      Math.PI / 2, false);\n    context.arc(offset.x + arrowWidth + radius, offset.y + toolTipHeight - radius, radius, Math.PI / 2, Math.PI, false);\n    if (toolTipOption.showArrow) {\n      context.lineTo(offset.x + arrowWidth, offset.y + 10 * opts.pix + 5 * opts.pix);\n      context.lineTo(offset.x, offset.y + 10 * opts.pix);\n    }\n  }\n  context.closePath();\n  context.fill();\n  if (toolTipOption.borderWidth > 0) {\n    context.stroke();\n  }\n  // draw legend\n  textList.forEach(function(item, index) {\n    if (item.color !== null) {\n      context.beginPath();\n      context.setFillStyle(item.color);\n      var startX = offset.x + arrowWidth + 2 * config.toolTipPadding;\n      var startY = offset.y + (config.toolTipLineHeight - config.fontSize) / 2 + config.toolTipLineHeight * index + config.toolTipPadding + 1;\n      if (isOverRightBorder) {\n        startX = offset.x - toolTipWidth - arrowWidth + 2 * config.toolTipPadding;\n      }\n      context.fillRect(startX, startY, legendWidth, config.fontSize);\n      context.closePath();\n    }\n  });\n  // draw text list\n  textList.forEach(function(item, index) {\n    var startX = offset.x + arrowWidth + 2 * config.toolTipPadding + legendWidth + legendMarginRight;\n    if (isOverRightBorder) {\n      startX = offset.x - toolTipWidth - arrowWidth + 2 * config.toolTipPadding + +legendWidth + legendMarginRight;\n    }\n    var startY = offset.y + (config.toolTipLineHeight - config.fontSize) / 2 + config.toolTipLineHeight * index + config.toolTipPadding;\n    context.beginPath();\n    context.setFontSize(config.fontSize);\n    context.setFillStyle(toolTipOption.fontColor);\n    context.fillText(item.text, startX, startY + config.fontSize);\n    context.closePath();\n    context.stroke();\n  });\n}\n\nfunction drawColumnDataPoints(series, opts, config, context) {\n  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    eachSpacing = xAxisData.eachSpacing;\n  let columnOption = assign({}, {\n    type: 'group',\n    width: eachSpacing / 2,\n    meterBorder: 4,\n    meterFillColor: '#FFFFFF',\n    barBorderCircle: false,\n    barBorderRadius: [],\n    seriesGap: 2,\n    linearType: 'none',\n    linearOpacity: 1,\n    customColor: [],\n    colorStop: 0,\n  }, opts.extra.column);\n  let calPoints = [];\n  context.save();\n  let leftNum = -2;\n  let rightNum = xAxisPoints.length + 2;\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;\n    rightNum = leftNum + opts.xAxis.itemCount + 4;\n  }\n  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {\n    drawToolTipSplitArea(opts.tooltip.offset.x, opts, config, context, eachSpacing);\n  }\n  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);\n  series.forEach(function(eachSeries, seriesIndex) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    var data = eachSeries.data;\n    switch (columnOption.type) {\n      case 'group':\n        var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n        var tooltipPoints = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);\n        calPoints.push(tooltipPoints);\n        points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);\n        for (let i = 0; i < points.length; i++) {\n          let item = points[i];\n          //fix issues/I27B1N yyoinge & Joeshu\n          if (item !== null && i > leftNum && i < rightNum) {\n            var startX = item.x - item.width / 2;\n            var height = opts.height - item.y - opts.area[2];\n            context.beginPath();\n            var fillColor = item.color || eachSeries.color\n            var strokeColor = item.color || eachSeries.color\n            if (columnOption.linearType !== 'none') {\n              var grd = context.createLinearGradient(startX, item.y, startX, opts.height - opts.area[2]);\n              //透明渐变\n              if (columnOption.linearType == 'opacity') {\n                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              } else {\n                grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));\n                grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex],columnOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              }\n              fillColor = grd\n            }\n            // 圆角边框\n            if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {\n              const left = startX;\n              const top = item.y;\n              const width = item.width;\n              const height = opts.height - opts.area[2] - item.y;\n              if (columnOption.barBorderCircle) {\n                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];\n              }\n              let [r0, r1, r2, r3] = columnOption.barBorderRadius;\n              let minRadius = Math.min(width/2,height/2);\n              r0 = r0 > minRadius ? minRadius : r0;\n              r1 = r1 > minRadius ? minRadius : r1;\n              r2 = r2 > minRadius ? minRadius : r2;\n              r3 = r3 > minRadius ? minRadius : r3;\n              r0 = r0 < 0 ? 0 : r0;\n              r1 = r1 < 0 ? 0 : r1;\n              r2 = r2 < 0 ? 0 : r2;\n              r3 = r3 < 0 ? 0 : r3;\n              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);\n              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);\n              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);\n              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);\n            } else {\n              context.moveTo(startX, item.y);\n              context.lineTo(startX + item.width, item.y);\n              context.lineTo(startX + item.width, opts.height - opts.area[2]);\n              context.lineTo(startX, opts.height - opts.area[2]);\n              context.lineTo(startX, item.y);\n              context.setLineWidth(1)\n              context.setStrokeStyle(strokeColor);\n            }\n            context.setFillStyle(fillColor);\n            context.closePath();\n            //context.stroke();\n            context.fill();\n          }\n        };\n        break;\n      case 'stack':\n        // 绘制堆叠数据图\n        var points = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);\n        calPoints.push(points);\n        points = fixColumeStackData(points, eachSpacing, series.length, seriesIndex, config, opts, series);\n        for (let i = 0; i < points.length; i++) {\n          let item = points[i];\n          if (item !== null && i > leftNum && i < rightNum) {\n            context.beginPath();\n            var fillColor = item.color || eachSeries.color;\n            var startX = item.x - item.width / 2 + 1;\n            var height = opts.height - item.y - opts.area[2];\n            var height0 = opts.height - item.y0 - opts.area[2];\n            if (seriesIndex > 0) {\n              height -= height0;\n            }\n            context.setFillStyle(fillColor);\n            context.moveTo(startX, item.y);\n            context.fillRect(startX, item.y, item.width, height);\n            context.closePath();\n            context.fill();\n          }\n        };\n        break;\n      case 'meter':\n        // 绘制温度计数据图\n        var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n        calPoints.push(points);\n        points = fixColumeMeterData(points, eachSpacing, series.length, seriesIndex, config, opts, columnOption.meterBorder);\n          for (let i = 0; i < points.length; i++) {\n            let item = points[i];\n            if (item !== null && i > leftNum && i < rightNum) {\n              //画背景颜色\n              context.beginPath();\n              if (seriesIndex == 0 && columnOption.meterBorder > 0) {\n                context.setStrokeStyle(eachSeries.color);\n                context.setLineWidth(columnOption.meterBorder * opts.pix);\n              }\n              if(seriesIndex == 0){\n                context.setFillStyle(columnOption.meterFillColor);\n              }else{\n                context.setFillStyle(item.color || eachSeries.color);\n              }\n              var startX = item.x - item.width / 2;\n              var height = opts.height - item.y - opts.area[2];\n              if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {\n                const left = startX;\n                const top = item.y;\n                const width = item.width;\n                const height = opts.height - opts.area[2] - item.y;\n                if (columnOption.barBorderCircle) {\n                  columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];\n                }\n                let [r0, r1, r2, r3] = columnOption.barBorderRadius;\n                let minRadius = Math.min(width/2,height/2);\n                r0 = r0 > minRadius ? minRadius : r0;\n                r1 = r1 > minRadius ? minRadius : r1;\n                r2 = r2 > minRadius ? minRadius : r2;\n                r3 = r3 > minRadius ? minRadius : r3;\n                r0 = r0 < 0 ? 0 : r0;\n                r1 = r1 < 0 ? 0 : r1;\n                r2 = r2 < 0 ? 0 : r2;\n                r3 = r3 < 0 ? 0 : r3;\n                context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);\n                context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);\n                context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);\n                context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);\n                context.fill();\n              }else{\n                context.moveTo(startX, item.y);\n                context.lineTo(startX + item.width, item.y);\n                context.lineTo(startX + item.width, opts.height - opts.area[2]);\n                context.lineTo(startX, opts.height - opts.area[2]);\n                context.lineTo(startX, item.y);\n                context.fill();\n              }\n              if (seriesIndex == 0 && columnOption.meterBorder > 0) {\n                context.closePath();\n                context.stroke();\n              }\n            }\n          }\n        break;\n    }\n  });\n\n  if (opts.dataLabel !== false && process === 1) {\n    series.forEach(function(eachSeries, seriesIndex) {\n      let ranges, minRange, maxRange;\n      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n      minRange = ranges.pop();\n      maxRange = ranges.shift();\n      var data = eachSeries.data;\n      switch (columnOption.type) {\n        case 'group':\n          var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n          points = fixColumeData(points, eachSpacing, series.length, seriesIndex, config, opts);\n          drawPointText(points, eachSeries, config, context, opts);\n          break;\n        case 'stack':\n          var points = getStackDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);\n          drawPointText(points, eachSeries, config, context, opts);\n          break;\n        case 'meter':\n          var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n          drawPointText(points, eachSeries, config, context, opts);\n          break;\n      }\n    });\n  }\n  context.restore();\n  return {\n    xAxisPoints: xAxisPoints,\n    calPoints: calPoints,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction drawMountDataPoints(series, opts, config, context) {\n  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    eachSpacing = xAxisData.eachSpacing;\n  let mountOption = assign({}, {\n    type: 'mount',\n    widthRatio: 1,\n    borderWidth: 1,\n    barBorderCircle: false,\n    barBorderRadius: [],\n    linearType: 'none',\n    linearOpacity: 1,\n    customColor: [],\n    colorStop: 0,\n  }, opts.extra.mount);\n  mountOption.widthRatio = mountOption.widthRatio <= 0 ? 0 : mountOption.widthRatio;\n  mountOption.widthRatio = mountOption.widthRatio >= 2 ? 2 : mountOption.widthRatio;\n  let calPoints = [];\n  context.save();\n  let leftNum = -2;\n  let rightNum = xAxisPoints.length + 2;\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;\n    rightNum = leftNum + opts.xAxis.itemCount + 4;\n  }\n  mountOption.customColor = fillCustomColor(mountOption.linearType, mountOption.customColor, series, config);\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[0]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    var points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, process);\n    switch (mountOption.type) {\n      case 'bar':\n        for (let i = 0; i < points.length; i++) {\n          let item = points[i];\n          if (item !== null && i > leftNum && i < rightNum) {\n            var startX = item.x - eachSpacing*mountOption.widthRatio/2;\n            var height = opts.height - item.y - opts.area[2];\n            context.beginPath();\n            var fillColor = item.color || series[i].color\n            var strokeColor = item.color || series[i].color\n            if (mountOption.linearType !== 'none') {\n              var grd = context.createLinearGradient(startX, item.y, startX, opts.height - opts.area[2]);\n              //透明渐变\n              if (mountOption.linearType == 'opacity') {\n                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              } else {\n                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));\n                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              }\n              fillColor = grd\n            }\n            // 圆角边框\n            if ((mountOption.barBorderRadius && mountOption.barBorderRadius.length === 4) || mountOption.barBorderCircle === true) {\n              const left = startX;\n              const top = item.y;\n              const width = item.width;\n              const height = opts.height - opts.area[2] - item.y - mountOption.borderWidth * opts.pix / 2; \n              if (mountOption.barBorderCircle) {\n                mountOption.barBorderRadius = [width / 2, width / 2, 0, 0];\n              }\n              let [r0, r1, r2, r3] = mountOption.barBorderRadius;\n              let minRadius = Math.min(width/2,height/2);\n              r0 = r0 > minRadius ? minRadius : r0;\n              r1 = r1 > minRadius ? minRadius : r1;\n              r2 = r2 > minRadius ? minRadius : r2;\n              r3 = r3 > minRadius ? minRadius : r3;\n              r0 = r0 < 0 ? 0 : r0;\n              r1 = r1 < 0 ? 0 : r1;\n              r2 = r2 < 0 ? 0 : r2;\n              r3 = r3 < 0 ? 0 : r3;\n              context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);\n              context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);\n              context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);\n              context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);\n            } else {\n              context.moveTo(startX, item.y);\n              context.lineTo(startX + item.width, item.y);\n              context.lineTo(startX + item.width, opts.height - opts.area[2]);\n              context.lineTo(startX, opts.height - opts.area[2]);\n              context.lineTo(startX, item.y);\n            }\n            context.setStrokeStyle(strokeColor);\n            context.setFillStyle(fillColor);\n            if(mountOption.borderWidth > 0){\n              context.setLineWidth(mountOption.borderWidth * opts.pix);\n              context.closePath();\n              context.stroke();\n            }\n            context.fill();\n          }\n        };\n        break;\n      case 'triangle':\n        for (let i = 0; i < points.length; i++) {\n          let item = points[i];\n          if (item !== null && i > leftNum && i < rightNum) {\n            var startX = item.x - eachSpacing*mountOption.widthRatio/2;\n            var height = opts.height - item.y - opts.area[2];\n            context.beginPath();\n            var fillColor = item.color || series[i].color\n            var strokeColor = item.color || series[i].color\n            if (mountOption.linearType !== 'none') {\n              var grd = context.createLinearGradient(startX, item.y, startX, opts.height - opts.area[2]);\n              //透明渐变\n              if (mountOption.linearType == 'opacity') {\n                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              } else {\n                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));\n                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              }\n              fillColor = grd\n            }\n            context.moveTo(startX, opts.height - opts.area[2]);\n            context.lineTo(item.x, item.y);\n            context.lineTo(startX + item.width, opts.height - opts.area[2]);\n            context.setStrokeStyle(strokeColor);\n            context.setFillStyle(fillColor);\n            if(mountOption.borderWidth > 0){\n              context.setLineWidth(mountOption.borderWidth * opts.pix);\n              context.stroke();\n            }\n            context.fill();\n          }\n        };\n        break;\n      case 'mount':\n        for (let i = 0; i < points.length; i++) {\n          let item = points[i];\n          if (item !== null && i > leftNum && i < rightNum) {\n            var startX = item.x - eachSpacing*mountOption.widthRatio/2;\n            var height = opts.height - item.y - opts.area[2];\n            context.beginPath();\n            var fillColor = item.color || series[i].color\n            var strokeColor = item.color || series[i].color\n            if (mountOption.linearType !== 'none') {\n              var grd = context.createLinearGradient(startX, item.y, startX, opts.height - opts.area[2]);\n              //透明渐变\n              if (mountOption.linearType == 'opacity') {\n                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              } else {\n                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));\n                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              }\n              fillColor = grd\n            }\n            context.moveTo(startX, opts.height - opts.area[2]);\n            context.bezierCurveTo(item.x - item.width/4, opts.height - opts.area[2], item.x - item.width/4, item.y, item.x, item.y);\n            context.bezierCurveTo(item.x + item.width/4, item.y, item.x + item.width/4, opts.height - opts.area[2], startX + item.width, opts.height - opts.area[2]);\n            context.setStrokeStyle(strokeColor);\n            context.setFillStyle(fillColor);\n            if(mountOption.borderWidth > 0){\n              context.setLineWidth(mountOption.borderWidth * opts.pix);\n              context.stroke();\n            }\n            context.fill();\n          }\n        };\n        break;\n      case 'sharp':\n        for (let i = 0; i < points.length; i++) {\n          let item = points[i];\n          if (item !== null && i > leftNum && i < rightNum) {\n            var startX = item.x - eachSpacing*mountOption.widthRatio/2;\n            var height = opts.height - item.y - opts.area[2];\n            context.beginPath();\n            var fillColor = item.color || series[i].color\n            var strokeColor = item.color || series[i].color\n            if (mountOption.linearType !== 'none') {\n              var grd = context.createLinearGradient(startX, item.y, startX, opts.height - opts.area[2]);\n              //透明渐变\n              if (mountOption.linearType == 'opacity') {\n                grd.addColorStop(0, hexToRgb(fillColor, mountOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              } else {\n                grd.addColorStop(0, hexToRgb(mountOption.customColor[series[i].linearIndex], mountOption.linearOpacity));\n                grd.addColorStop(mountOption.colorStop, hexToRgb(mountOption.customColor[series[i].linearIndex],mountOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              }\n              fillColor = grd\n            }\n            context.moveTo(startX, opts.height - opts.area[2]);\n            context.quadraticCurveTo(item.x - 0, opts.height - opts.area[2] - height/4, item.x, item.y);\n            context.quadraticCurveTo(item.x + 0, opts.height - opts.area[2] - height/4, startX + item.width, opts.height - opts.area[2])\n            context.setStrokeStyle(strokeColor);\n            context.setFillStyle(fillColor);\n            if(mountOption.borderWidth > 0){\n              context.setLineWidth(mountOption.borderWidth * opts.pix);\n              context.stroke();\n            }\n            context.fill();\n          }\n        };\n        break;\n    }\n\n  if (opts.dataLabel !== false && process === 1) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[0]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    var points = getMountDataPoints(series, minRange, maxRange, xAxisPoints, eachSpacing, opts, mountOption, process);\n    drawMountPointText(points, series, config, context, opts);\n  }\n  context.restore();\n  return {\n    xAxisPoints: xAxisPoints,\n    calPoints: points,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction drawBarDataPoints(series, opts, config, context) {\n  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  let yAxisPoints = [];\n  let eachSpacing = (opts.height - opts.area[0] - opts.area[2])/opts.categories.length;\n  for (let i = 0; i < opts.categories.length; i++) {\n    yAxisPoints.push(opts.area[0] + eachSpacing / 2 + eachSpacing * i);\n  }\n  let columnOption = assign({}, {\n    type: 'group',\n    width: eachSpacing / 2,\n    meterBorder: 4,\n    meterFillColor: '#FFFFFF',\n    barBorderCircle: false,\n    barBorderRadius: [],\n    seriesGap: 2,\n    linearType: 'none',\n    linearOpacity: 1,\n    customColor: [],\n    colorStop: 0,\n  }, opts.extra.bar);\n  let calPoints = [];\n  context.save();\n  let leftNum = -2;\n  let rightNum = yAxisPoints.length + 2;\n  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {\n    drawBarToolTipSplitArea(opts.tooltip.offset.y, opts, config, context, eachSpacing);\n  }\n  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);\n  series.forEach(function(eachSeries, seriesIndex) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.xAxisData.ranges);\n    maxRange = ranges.pop();\n    minRange = ranges.shift();\n    var data = eachSeries.data;\n    switch (columnOption.type) {\n      case 'group':\n        var points = getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, process);\n        var tooltipPoints = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);\n        calPoints.push(tooltipPoints);\n        points = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);\n        for (let i = 0; i < points.length; i++) {\n          let item = points[i];\n          //fix issues/I27B1N yyoinge & Joeshu\n          if (item !== null && i > leftNum && i < rightNum) {\n            //var startX = item.x - item.width / 2;\n            var startX = opts.area[3];\n            var startY = item.y - item.width / 2;\n            var height = item.height;\n            context.beginPath();\n            var fillColor = item.color || eachSeries.color\n            var strokeColor = item.color || eachSeries.color\n            if (columnOption.linearType !== 'none') {\n              var grd = context.createLinearGradient(startX, item.y, item.x, item.y);\n              //透明渐变\n              if (columnOption.linearType == 'opacity') {\n                grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              } else {\n                grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));\n                grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex],columnOption.linearOpacity));\n                grd.addColorStop(1, hexToRgb(fillColor, 1));\n              }\n              fillColor = grd\n            }\n            // 圆角边框\n            if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle === true) {\n              const left = startX;\n              const width = item.width;\n              const top = item.y - item.width / 2;\n              const height = item.height;\n              if (columnOption.barBorderCircle) {\n                columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];\n              }\n              let [r0, r1, r2, r3] = columnOption.barBorderRadius;\n              let minRadius = Math.min(width/2,height/2);\n              r0 = r0 > minRadius ? minRadius : r0;\n              r1 = r1 > minRadius ? minRadius : r1;\n              r2 = r2 > minRadius ? minRadius : r2;\n              r3 = r3 > minRadius ? minRadius : r3;\n              r0 = r0 < 0 ? 0 : r0;\n              r1 = r1 < 0 ? 0 : r1;\n              r2 = r2 < 0 ? 0 : r2;\n              r3 = r3 < 0 ? 0 : r3;\n              \n              context.arc(left + r3, top + r3, r3, -Math.PI, -Math.PI / 2);\n              context.arc(item.x - r0, top + r0, r0, -Math.PI / 2, 0);\n              context.arc(item.x - r1, top + width - r1, r1, 0, Math.PI / 2);\n              context.arc(left + r2, top + width - r2, r2, Math.PI / 2, Math.PI);\n            } else {\n              context.moveTo(startX, startY);\n              context.lineTo(item.x, startY);\n              context.lineTo(item.x, startY + item.width);\n              context.lineTo(startX, startY + item.width);\n              context.lineTo(startX, startY);\n              context.setLineWidth(1)\n              context.setStrokeStyle(strokeColor);\n            }\n            context.setFillStyle(fillColor);\n            context.closePath();\n            //context.stroke();\n            context.fill();\n          }\n        };\n        break;\n      case 'stack':\n        // 绘制堆叠数据图\n        var points = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);\n        calPoints.push(points);\n        points = fixBarStackData(points, eachSpacing, series.length, seriesIndex, config, opts, series);\n        for (let i = 0; i < points.length; i++) {\n          let item = points[i];\n          if (item !== null && i > leftNum && i < rightNum) {\n            context.beginPath();\n            var fillColor = item.color || eachSeries.color;\n            var startX = item.x0;\n            context.setFillStyle(fillColor);\n            context.moveTo(startX, item.y - item.width/2);\n            context.fillRect(startX, item.y - item.width/2, item.height , item.width);\n            context.closePath();\n            context.fill();\n          }\n        };\n        break;\n    }\n  });\n\n  if (opts.dataLabel !== false && process === 1) {\n    series.forEach(function(eachSeries, seriesIndex) {\n      let ranges, minRange, maxRange;\n      ranges = [].concat(opts.chartData.xAxisData.ranges);\n      maxRange = ranges.pop();\n      minRange = ranges.shift();\n      var data = eachSeries.data;\n      switch (columnOption.type) {\n        case 'group':\n          var points = getBarDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, process);\n          points = fixBarData(points, eachSpacing, series.length, seriesIndex, config, opts);\n          drawBarPointText(points, eachSeries, config, context, opts);\n          break;\n        case 'stack':\n          var points = getBarStackDataPoints(data, minRange, maxRange, yAxisPoints, eachSpacing, opts, config, seriesIndex, series, process);\n          drawBarPointText(points, eachSeries, config, context, opts);\n          break;\n      }\n    });\n  }\n  return {\n    yAxisPoints: yAxisPoints,\n    calPoints: calPoints,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction drawCandleDataPoints(series, seriesMA, opts, config, context) {\n  var process = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;\n  var candleOption = assign({}, {\n    color: {},\n    average: {}\n  }, opts.extra.candle);\n  candleOption.color = assign({}, {\n    upLine: '#f04864',\n    upFill: '#f04864',\n    downLine: '#2fc25b',\n    downFill: '#2fc25b'\n  }, candleOption.color);\n  candleOption.average = assign({}, {\n    show: false,\n    name: [],\n    day: [],\n    color: config.color\n  }, candleOption.average);\n  opts.extra.candle = candleOption;\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    eachSpacing = xAxisData.eachSpacing;\n  let calPoints = [];\n  context.save();\n  let leftNum = -2;\n  let rightNum = xAxisPoints.length + 2;\n  let leftSpace = 0;\n  let rightSpace = opts.width + eachSpacing;\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;\n    rightNum = leftNum + opts.xAxis.itemCount + 4;\n    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];\n    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;\n  }\n  //画均线\n  if (candleOption.average.show || seriesMA) { //Merge pull request !12 from 邱贵翔\n    seriesMA.forEach(function(eachSeries, seriesIndex) {\n      let ranges, minRange, maxRange;\n      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n      minRange = ranges.pop();\n      maxRange = ranges.shift();\n      var data = eachSeries.data;\n      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n      var splitPointList = splitPoints(points,eachSeries);\n      for (let i = 0; i < splitPointList.length; i++) {\n        let points = splitPointList[i];\n        context.beginPath();\n        context.setStrokeStyle(eachSeries.color);\n        context.setLineWidth(1);\n        if (points.length === 1) {\n          context.moveTo(points[0].x, points[0].y);\n          context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);\n        } else {\n          context.moveTo(points[0].x, points[0].y);\n          let startPoint = 0;\n          for (let j = 0; j < points.length; j++) {\n            let item = points[j];\n            if (startPoint == 0 && item.x > leftSpace) {\n              context.moveTo(item.x, item.y);\n              startPoint = 1;\n            }\n            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n              var ctrlPoint = createCurveControlPoints(points, j - 1);\n              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x,\n                item.y);\n            }\n          }\n          context.moveTo(points[0].x, points[0].y);\n        }\n        context.closePath();\n        context.stroke();\n      }\n    });\n  }\n  //画K线\n  series.forEach(function(eachSeries, seriesIndex) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    var data = eachSeries.data;\n    var points = getCandleDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n    calPoints.push(points);\n    var splitPointList = splitPoints(points,eachSeries);\n    for (let i = 0; i < splitPointList[0].length; i++) {\n      if (i > leftNum && i < rightNum) {\n        let item = splitPointList[0][i];\n        context.beginPath();\n        //如果上涨\n        if (data[i][1] - data[i][0] > 0) {\n          context.setStrokeStyle(candleOption.color.upLine);\n          context.setFillStyle(candleOption.color.upFill);\n          context.setLineWidth(1 * opts.pix);\n          context.moveTo(item[3].x, item[3].y); //顶点\n          context.lineTo(item[1].x, item[1].y); //收盘中间点\n          context.lineTo(item[1].x - eachSpacing / 4, item[1].y); //收盘左侧点\n          context.lineTo(item[0].x - eachSpacing / 4, item[0].y); //开盘左侧点\n          context.lineTo(item[0].x, item[0].y); //开盘中间点\n          context.lineTo(item[2].x, item[2].y); //底点\n          context.lineTo(item[0].x, item[0].y); //开盘中间点\n          context.lineTo(item[0].x + eachSpacing / 4, item[0].y); //开盘右侧点\n          context.lineTo(item[1].x + eachSpacing / 4, item[1].y); //收盘右侧点\n          context.lineTo(item[1].x, item[1].y); //收盘中间点\n          context.moveTo(item[3].x, item[3].y); //顶点\n        } else {\n          context.setStrokeStyle(candleOption.color.downLine);\n          context.setFillStyle(candleOption.color.downFill);\n          context.setLineWidth(1 * opts.pix);\n          context.moveTo(item[3].x, item[3].y); //顶点\n          context.lineTo(item[0].x, item[0].y); //开盘中间点\n          context.lineTo(item[0].x - eachSpacing / 4, item[0].y); //开盘左侧点\n          context.lineTo(item[1].x - eachSpacing / 4, item[1].y); //收盘左侧点\n          context.lineTo(item[1].x, item[1].y); //收盘中间点\n          context.lineTo(item[2].x, item[2].y); //底点\n          context.lineTo(item[1].x, item[1].y); //收盘中间点\n          context.lineTo(item[1].x + eachSpacing / 4, item[1].y); //收盘右侧点\n          context.lineTo(item[0].x + eachSpacing / 4, item[0].y); //开盘右侧点\n          context.lineTo(item[0].x, item[0].y); //开盘中间点\n          context.moveTo(item[3].x, item[3].y); //顶点\n        }\n        context.closePath();\n        context.fill();\n        context.stroke();\n      }\n    }\n  });\n  context.restore();\n  return {\n    xAxisPoints: xAxisPoints,\n    calPoints: calPoints,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction drawAreaDataPoints(series, opts, config, context) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var areaOption = assign({}, {\n    type: 'straight',\n    opacity: 0.2,\n    addLine: false,\n    width: 2,\n    gradient: false\n  }, opts.extra.area);\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    eachSpacing = xAxisData.eachSpacing;\n  let endY = opts.height - opts.area[2];\n  let calPoints = [];\n  context.save();\n  let leftSpace = 0;\n  let rightSpace = opts.width + eachSpacing;\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];\n    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;\n  }\n  series.forEach(function(eachSeries, seriesIndex) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    let data = eachSeries.data;\n    let points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n    calPoints.push(points);\n    let splitPointList = splitPoints(points,eachSeries);\n    for (let i = 0; i < splitPointList.length; i++) {\n      let points = splitPointList[i];\n      // 绘制区域数\n      context.beginPath();\n      context.setStrokeStyle(hexToRgb(eachSeries.color, areaOption.opacity));\n      if (areaOption.gradient) {\n        let gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);\n        gradient.addColorStop('0', hexToRgb(eachSeries.color, areaOption.opacity));\n        gradient.addColorStop('1.0', hexToRgb(\"#FFFFFF\", 0.1));\n        context.setFillStyle(gradient);\n      } else {\n        context.setFillStyle(hexToRgb(eachSeries.color, areaOption.opacity));\n      }\n      context.setLineWidth(areaOption.width * opts.pix);\n      if (points.length > 1) {\n        let firstPoint = points[0];\n        let lastPoint = points[points.length - 1];\n        context.moveTo(firstPoint.x, firstPoint.y);\n        let startPoint = 0;\n        if (areaOption.type === 'curve') {\n          for (let j = 0; j < points.length; j++) {\n            let item = points[j];\n            if (startPoint == 0 && item.x > leftSpace) {\n              context.moveTo(item.x, item.y);\n              startPoint = 1;\n            }\n            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n              let ctrlPoint = createCurveControlPoints(points, j - 1);\n              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);\n            }\n          };\n        } \n        if (areaOption.type === 'straight') {\n          for (let j = 0; j < points.length; j++) {\n            let item = points[j];\n            if (startPoint == 0 && item.x > leftSpace) {\n              context.moveTo(item.x, item.y);\n              startPoint = 1;\n            }\n            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n              context.lineTo(item.x, item.y);\n            }\n          };\n        }\n        if (areaOption.type === 'step') {\n          for (let j = 0; j < points.length; j++) {\n            let item = points[j];\n            if (startPoint == 0 && item.x > leftSpace) {\n              context.moveTo(item.x, item.y);\n              startPoint = 1;\n            }\n            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n              context.lineTo(item.x, points[j - 1].y);\n              context.lineTo(item.x, item.y);\n            }\n          };\n        }\n        context.lineTo(lastPoint.x, endY);\n        context.lineTo(firstPoint.x, endY);\n        context.lineTo(firstPoint.x, firstPoint.y);\n      } else {\n        let item = points[0];\n        context.moveTo(item.x - eachSpacing / 2, item.y);\n        context.lineTo(item.x + eachSpacing / 2, item.y);\n        context.lineTo(item.x + eachSpacing / 2, endY);\n        context.lineTo(item.x - eachSpacing / 2, endY);\n        context.moveTo(item.x - eachSpacing / 2, item.y);\n      }\n      context.closePath();\n      context.fill();\n      //画连线\n      if (areaOption.addLine) {\n        if (eachSeries.lineType == 'dash') {\n          let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;\n          dashLength *= opts.pix;\n          context.setLineDash([dashLength, dashLength]);\n        }\n        context.beginPath();\n        context.setStrokeStyle(eachSeries.color);\n        context.setLineWidth(areaOption.width * opts.pix);\n        if (points.length === 1) {\n          context.moveTo(points[0].x, points[0].y);\n          context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);\n        } else {\n          context.moveTo(points[0].x, points[0].y);\n          let startPoint = 0;\n          if (areaOption.type === 'curve') {\n            for (let j = 0; j < points.length; j++) {\n              let item = points[j];\n              if (startPoint == 0 && item.x > leftSpace) {\n                context.moveTo(item.x, item.y);\n                startPoint = 1;\n              }\n              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n                let ctrlPoint = createCurveControlPoints(points, j - 1);\n                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);\n              }\n            };\n          }\n          if (areaOption.type === 'straight') {\n            for (let j = 0; j < points.length; j++) {\n              let item = points[j];\n              if (startPoint == 0 && item.x > leftSpace) {\n                context.moveTo(item.x, item.y);\n                startPoint = 1;\n              }\n              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n                context.lineTo(item.x, item.y);\n              }\n            };\n          }\n          if (areaOption.type === 'step') {\n            for (let j = 0; j < points.length; j++) {\n              let item = points[j];\n              if (startPoint == 0 && item.x > leftSpace) {\n                context.moveTo(item.x, item.y);\n                startPoint = 1;\n              }\n              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n                context.lineTo(item.x, points[j - 1].y);\n                context.lineTo(item.x, item.y);\n              }\n            };\n          }\n          context.moveTo(points[0].x, points[0].y);\n        }\n        context.stroke();\n        context.setLineDash([]);\n      }\n    }\n    //画点\n    if (opts.dataPointShape !== false) {\n      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);\n    }\n  });\n\n  if (opts.dataLabel !== false && process === 1) {\n    series.forEach(function(eachSeries, seriesIndex) {\n      let ranges, minRange, maxRange;\n      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n      minRange = ranges.pop();\n      maxRange = ranges.shift();\n      var data = eachSeries.data;\n      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n      drawPointText(points, eachSeries, config, context, opts);\n    });\n  }\n  context.restore();\n  return {\n    xAxisPoints: xAxisPoints,\n    calPoints: calPoints,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction drawScatterDataPoints(series, opts, config, context) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var scatterOption = assign({}, {\n    type: 'circle'\n  }, opts.extra.scatter);\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    eachSpacing = xAxisData.eachSpacing;\n  var calPoints = [];\n  context.save();\n  let leftSpace = 0;\n  let rightSpace = opts.width + eachSpacing;\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];\n    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;\n  }\n  series.forEach(function(eachSeries, seriesIndex) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    var data = eachSeries.data;\n    var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n    context.beginPath();\n    context.setStrokeStyle(eachSeries.color);\n    context.setFillStyle(eachSeries.color);\n    context.setLineWidth(1 * opts.pix);\n    var shape = eachSeries.pointShape;\n    if (shape === 'diamond') {\n      points.forEach(function(item, index) {\n        if (item !== null) {\n          context.moveTo(item.x, item.y - 4.5);\n          context.lineTo(item.x - 4.5, item.y);\n          context.lineTo(item.x, item.y + 4.5);\n          context.lineTo(item.x + 4.5, item.y);\n          context.lineTo(item.x, item.y - 4.5);\n        }\n      });\n    } else if (shape === 'circle') {\n      points.forEach(function(item, index) {\n        if (item !== null) {\n          context.moveTo(item.x + 2.5 * opts.pix, item.y);\n          context.arc(item.x, item.y, 3 * opts.pix, 0, 2 * Math.PI, false);\n        }\n      });\n    } else if (shape === 'square') {\n      points.forEach(function(item, index) {\n        if (item !== null) {\n          context.moveTo(item.x - 3.5, item.y - 3.5);\n          context.rect(item.x - 3.5, item.y - 3.5, 7, 7);\n        }\n      });\n    } else if (shape === 'triangle') {\n      points.forEach(function(item, index) {\n        if (item !== null) {\n          context.moveTo(item.x, item.y - 4.5);\n          context.lineTo(item.x - 4.5, item.y + 4.5);\n          context.lineTo(item.x + 4.5, item.y + 4.5);\n          context.lineTo(item.x, item.y - 4.5);\n        }\n      });\n    } else if (shape === 'triangle') {\n      return;\n    }\n    context.closePath();\n    context.fill();\n    context.stroke();\n  });\n  if (opts.dataLabel !== false && process === 1) {\n    series.forEach(function(eachSeries, seriesIndex) {\n      let ranges, minRange, maxRange;\n      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n      minRange = ranges.pop();\n      maxRange = ranges.shift();\n      var data = eachSeries.data;\n      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n      drawPointText(points, eachSeries, config, context, opts);\n    });\n  }\n  context.restore();\n  return {\n    xAxisPoints: xAxisPoints,\n    calPoints: calPoints,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction drawBubbleDataPoints(series, opts, config, context) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var bubbleOption = assign({}, {\n    opacity: 1,\n    border:2\n  }, opts.extra.bubble);\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    eachSpacing = xAxisData.eachSpacing;\n  var calPoints = [];\n  context.save();\n  let leftSpace = 0;\n  let rightSpace = opts.width + eachSpacing;\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];\n    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;\n  }\n  series.forEach(function(eachSeries, seriesIndex) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    var data = eachSeries.data;\n    var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n    context.beginPath();\n    context.setStrokeStyle(eachSeries.color);\n    context.setLineWidth(bubbleOption.border * opts.pix);\n    context.setFillStyle(hexToRgb(eachSeries.color, bubbleOption.opacity));\n    points.forEach(function(item, index) {\n      context.moveTo(item.x + item.r, item.y);\n      context.arc(item.x, item.y, item.r * opts.pix, 0, 2 * Math.PI, false);\n    });\n    context.closePath();\n    context.fill();\n    context.stroke();\n    \n    if (opts.dataLabel !== false && process === 1) {\n      points.forEach(function(item, index) {\n        context.beginPath();\n        var fontSize = series.textSize * opts.pix || config.fontSize;\n        context.setFontSize(fontSize);\n        context.setFillStyle(series.textColor || \"#FFFFFF\");\n        context.setTextAlign('center');\n        context.fillText(String(item.t), item.x, item.y + fontSize/2);\n        context.closePath();\n        context.stroke();\n        context.setTextAlign('left');\n      });\n    }\n  });\n  context.restore();\n  return {\n    xAxisPoints: xAxisPoints,\n    calPoints: calPoints,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction drawLineDataPoints(series, opts, config, context) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var lineOption = assign({}, {\n    type: 'straight',\n    width: 2\n  }, opts.extra.line);\n  lineOption.width *= opts.pix;\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    eachSpacing = xAxisData.eachSpacing;\n  var calPoints = [];\n  context.save();\n  let leftSpace = 0;\n  let rightSpace = opts.width + eachSpacing;\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];\n    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;\n  }\n  series.forEach(function(eachSeries, seriesIndex) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    var data = eachSeries.data;\n    var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n    calPoints.push(points);\n    var splitPointList = splitPoints(points,eachSeries);\n    if (eachSeries.lineType == 'dash') {\n      let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;\n      dashLength *= opts.pix;\n      context.setLineDash([dashLength, dashLength]);\n    }\n    context.beginPath();\n    context.setStrokeStyle(eachSeries.color);\n    context.setLineWidth(lineOption.width);\n    splitPointList.forEach(function(points, index) {\n      if (points.length === 1) {\n        context.moveTo(points[0].x, points[0].y);\n        context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);\n      } else {\n        context.moveTo(points[0].x, points[0].y);\n        let startPoint = 0;\n        if (lineOption.type === 'curve') {\n          for (let j = 0; j < points.length; j++) {\n            let item = points[j];\n            if (startPoint == 0 && item.x > leftSpace) {\n              context.moveTo(item.x, item.y);\n              startPoint = 1;\n            }\n            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n              var ctrlPoint = createCurveControlPoints(points, j - 1);\n              context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);\n            }\n          };\n        }\n        if (lineOption.type === 'straight') {\n          for (let j = 0; j < points.length; j++) {\n            let item = points[j];\n            if (startPoint == 0 && item.x > leftSpace) {\n              context.moveTo(item.x, item.y);\n              startPoint = 1;\n            }\n            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n              context.lineTo(item.x, item.y);\n            }\n          };\n        }\n        if (lineOption.type === 'step') {\n          for (let j = 0; j < points.length; j++) {\n            let item = points[j];\n            if (startPoint == 0 && item.x > leftSpace) {\n              context.moveTo(item.x, item.y);\n              startPoint = 1;\n            }\n            if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n              context.lineTo(item.x, points[j - 1].y);\n              context.lineTo(item.x, item.y);\n            }\n          };\n        }\n        context.moveTo(points[0].x, points[0].y);\n      }\n    });\n    context.stroke();\n    context.setLineDash([]);\n    if (opts.dataPointShape !== false) {\n      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);\n    }\n  });\n  if (opts.dataLabel !== false && process === 1) {\n    series.forEach(function(eachSeries, seriesIndex) {\n      let ranges, minRange, maxRange;\n      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n      minRange = ranges.pop();\n      maxRange = ranges.shift();\n      var data = eachSeries.data;\n      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n      drawPointText(points, eachSeries, config, context, opts);\n    });\n  }\n  context.restore();\n  return {\n    xAxisPoints: xAxisPoints,\n    calPoints: calPoints,\n    eachSpacing: eachSpacing\n  };\n}\n\nfunction drawMixDataPoints(series, opts, config, context) {\n  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    eachSpacing = xAxisData.eachSpacing;\n  let columnOption = assign({}, {\n    width: eachSpacing / 2,\n    barBorderCircle: false,\n    barBorderRadius: [],\n    seriesGap: 2,\n    linearType: 'none',\n    linearOpacity: 1,\n    customColor: [],\n    colorStop: 0,\n  }, opts.extra.mix.column);\n  let areaOption = assign({}, {\n    opacity: 0.2,\n    gradient: false\n  }, opts.extra.mix.area);\n  let endY = opts.height - opts.area[2];\n  let calPoints = [];\n  var columnIndex = 0;\n  var columnLength = 0;\n  series.forEach(function(eachSeries, seriesIndex) {\n    if (eachSeries.type == 'column') {\n      columnLength += 1;\n    }\n  });\n  context.save();\n  let leftNum = -2;\n  let rightNum = xAxisPoints.length + 2;\n  let leftSpace = 0;\n  let rightSpace = opts.width + eachSpacing;\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n    leftNum = Math.floor(-opts._scrollDistance_ / eachSpacing) - 2;\n    rightNum = leftNum + opts.xAxis.itemCount + 4;\n    leftSpace = -opts._scrollDistance_ - eachSpacing * 2 + opts.area[3];\n    rightSpace = leftSpace + (opts.xAxis.itemCount + 4) * eachSpacing;\n  }\n  columnOption.customColor = fillCustomColor(columnOption.linearType, columnOption.customColor, series, config);\n  series.forEach(function(eachSeries, seriesIndex) {\n    let ranges, minRange, maxRange;\n    ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n    minRange = ranges.pop();\n    maxRange = ranges.shift();\n    var data = eachSeries.data;\n    var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n    calPoints.push(points);\n    // 绘制柱状数据图\n    if (eachSeries.type == 'column') {\n      points = fixColumeData(points, eachSpacing, columnLength, columnIndex, config, opts);\n      for (let i = 0; i < points.length; i++) {\n        let item = points[i];\n        if (item !== null && i > leftNum && i < rightNum) {\n          var startX = item.x - item.width / 2;\n          var height = opts.height - item.y - opts.area[2];\n          context.beginPath();\n          var fillColor = item.color || eachSeries.color\n          var strokeColor = item.color || eachSeries.color\n          if (columnOption.linearType !== 'none') {\n            var grd = context.createLinearGradient(startX, item.y, startX, opts.height - opts.area[2]);\n            //透明渐变\n            if (columnOption.linearType == 'opacity') {\n              grd.addColorStop(0, hexToRgb(fillColor, columnOption.linearOpacity));\n              grd.addColorStop(1, hexToRgb(fillColor, 1));\n            } else {\n              grd.addColorStop(0, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));\n              grd.addColorStop(columnOption.colorStop, hexToRgb(columnOption.customColor[eachSeries.linearIndex], columnOption.linearOpacity));\n              grd.addColorStop(1, hexToRgb(fillColor, 1));\n            }\n            fillColor = grd\n          }\n          // 圆角边框\n          if ((columnOption.barBorderRadius && columnOption.barBorderRadius.length === 4) || columnOption.barBorderCircle) {\n            const left = startX;\n            const top = item.y;\n            const width = item.width;\n            const height = opts.height - opts.area[2] - item.y;\n            if (columnOption.barBorderCircle) {\n              columnOption.barBorderRadius = [width / 2, width / 2, 0, 0];\n            }\n            let [r0, r1, r2, r3] = columnOption.barBorderRadius;\n            let minRadius = Math.min(width/2,height/2);\n            r0 = r0 > minRadius ? minRadius : r0;\n            r1 = r1 > minRadius ? minRadius : r1;\n            r2 = r2 > minRadius ? minRadius : r2;\n            r3 = r3 > minRadius ? minRadius : r3;\n            r0 = r0 < 0 ? 0 : r0;\n            r1 = r1 < 0 ? 0 : r1;\n            r2 = r2 < 0 ? 0 : r2;\n            r3 = r3 < 0 ? 0 : r3;\n            context.arc(left + r0, top + r0, r0, -Math.PI, -Math.PI / 2);\n            context.arc(left + width - r1, top + r1, r1, -Math.PI / 2, 0);\n            context.arc(left + width - r2, top + height - r2, r2, 0, Math.PI / 2);\n            context.arc(left + r3, top + height - r3, r3, Math.PI / 2, Math.PI);\n          } else {\n            context.moveTo(startX, item.y);\n            context.lineTo(startX + item.width, item.y);\n            context.lineTo(startX + item.width, opts.height - opts.area[2]);\n            context.lineTo(startX, opts.height - opts.area[2]);\n            context.lineTo(startX, item.y);\n            context.setLineWidth(1)\n            context.setStrokeStyle(strokeColor);\n          }\n          context.setFillStyle(fillColor);\n          context.closePath();\n          context.fill();\n        }\n      }\n      columnIndex += 1;\n    }\n    //绘制区域图数据\n    if (eachSeries.type == 'area') {\n      let splitPointList = splitPoints(points,eachSeries);\n      for (let i = 0; i < splitPointList.length; i++) {\n        let points = splitPointList[i];\n        // 绘制区域数据\n        context.beginPath();\n        context.setStrokeStyle(eachSeries.color);\n        context.setStrokeStyle(hexToRgb(eachSeries.color, areaOption.opacity));\n        if (areaOption.gradient) {\n          let gradient = context.createLinearGradient(0, opts.area[0], 0, opts.height - opts.area[2]);\n          gradient.addColorStop('0', hexToRgb(eachSeries.color, areaOption.opacity));\n          gradient.addColorStop('1.0', hexToRgb(\"#FFFFFF\", 0.1));\n          context.setFillStyle(gradient);\n        } else {\n          context.setFillStyle(hexToRgb(eachSeries.color, areaOption.opacity));\n        }\n        context.setLineWidth(2 * opts.pix);\n        if (points.length > 1) {\n          var firstPoint = points[0];\n          let lastPoint = points[points.length - 1];\n          context.moveTo(firstPoint.x, firstPoint.y);\n          let startPoint = 0;\n          if (eachSeries.style === 'curve') {\n            for (let j = 0; j < points.length; j++) {\n              let item = points[j];\n              if (startPoint == 0 && item.x > leftSpace) {\n                context.moveTo(item.x, item.y);\n                startPoint = 1;\n              }\n              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n                var ctrlPoint = createCurveControlPoints(points, j - 1);\n                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y, item.x, item.y);\n              }\n            };\n          } else {\n            for (let j = 0; j < points.length; j++) {\n              let item = points[j];\n              if (startPoint == 0 && item.x > leftSpace) {\n                context.moveTo(item.x, item.y);\n                startPoint = 1;\n              }\n              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n                context.lineTo(item.x, item.y);\n              }\n            };\n          }\n          context.lineTo(lastPoint.x, endY);\n          context.lineTo(firstPoint.x, endY);\n          context.lineTo(firstPoint.x, firstPoint.y);\n        } else {\n          let item = points[0];\n          context.moveTo(item.x - eachSpacing / 2, item.y);\n          context.lineTo(item.x + eachSpacing / 2, item.y);\n          context.lineTo(item.x + eachSpacing / 2, endY);\n          context.lineTo(item.x - eachSpacing / 2, endY);\n          context.moveTo(item.x - eachSpacing / 2, item.y);\n        }\n        context.closePath();\n        context.fill();\n      }\n    }\n    // 绘制折线数据图\n    if (eachSeries.type == 'line') {\n      var splitPointList = splitPoints(points,eachSeries);\n      splitPointList.forEach(function(points, index) {\n        if (eachSeries.lineType == 'dash') {\n          let dashLength = eachSeries.dashLength ? eachSeries.dashLength : 8;\n          dashLength *= opts.pix;\n          context.setLineDash([dashLength, dashLength]);\n        }\n        context.beginPath();\n        context.setStrokeStyle(eachSeries.color);\n        context.setLineWidth(2 * opts.pix);\n        if (points.length === 1) {\n          context.moveTo(points[0].x, points[0].y);\n          context.arc(points[0].x, points[0].y, 1, 0, 2 * Math.PI);\n        } else {\n          context.moveTo(points[0].x, points[0].y);\n          let startPoint = 0;\n          if (eachSeries.style == 'curve') {\n            for (let j = 0; j < points.length; j++) {\n              let item = points[j];\n              if (startPoint == 0 && item.x > leftSpace) {\n                context.moveTo(item.x, item.y);\n                startPoint = 1;\n              }\n              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n                var ctrlPoint = createCurveControlPoints(points, j - 1);\n                context.bezierCurveTo(ctrlPoint.ctrA.x, ctrlPoint.ctrA.y, ctrlPoint.ctrB.x, ctrlPoint.ctrB.y,\n                  item.x, item.y);\n              }\n            }\n          } else {\n            for (let j = 0; j < points.length; j++) {\n              let item = points[j];\n              if (startPoint == 0 && item.x > leftSpace) {\n                context.moveTo(item.x, item.y);\n                startPoint = 1;\n              }\n              if (j > 0 && item.x > leftSpace && item.x < rightSpace) {\n                context.lineTo(item.x, item.y);\n              }\n            }\n          }\n          context.moveTo(points[0].x, points[0].y);\n        }\n        context.stroke();\n        context.setLineDash([]);\n      });\n    }\n    // 绘制点数据图\n    if (eachSeries.type == 'point') {\n      eachSeries.addPoint = true;\n    }\n    if (eachSeries.addPoint == true && eachSeries.type !== 'column') {\n      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);\n    }\n  });\n  if (opts.dataLabel !== false && process === 1) {\n    var columnIndex = 0;\n    series.forEach(function(eachSeries, seriesIndex) {\n      let ranges, minRange, maxRange;\n      ranges = [].concat(opts.chartData.yAxisData.ranges[eachSeries.index]);\n      minRange = ranges.pop();\n      maxRange = ranges.shift();\n      var data = eachSeries.data;\n      var points = getDataPoints(data, minRange, maxRange, xAxisPoints, eachSpacing, opts, config, process);\n      if (eachSeries.type !== 'column') {\n        drawPointText(points, eachSeries, config, context, opts);\n      } else {\n        points = fixColumeData(points, eachSpacing, columnLength, columnIndex, config, opts);\n        drawPointText(points, eachSeries, config, context, opts);\n        columnIndex += 1;\n      }\n    });\n  }\n  context.restore();\n  return {\n    xAxisPoints: xAxisPoints,\n    calPoints: calPoints,\n    eachSpacing: eachSpacing,\n  }\n}\n\n\nfunction drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints) {\n  var toolTipOption = opts.extra.tooltip || {};\n  if (toolTipOption.horizentalLine && opts.tooltip && process === 1 && (opts.type == 'line' || opts.type == 'area' || opts.type == 'column' || opts.type == 'mount' || opts.type == 'candle' || opts.type == 'mix')) {\n    drawToolTipHorizentalLine(opts, config, context, eachSpacing, xAxisPoints)\n  }\n  context.save();\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0 && opts.enableScroll === true) {\n    context.translate(opts._scrollDistance_, 0);\n  }\n  if (opts.tooltip && opts.tooltip.textList && opts.tooltip.textList.length && process === 1) {\n    drawToolTip(opts.tooltip.textList, opts.tooltip.offset, opts, config, context, eachSpacing, xAxisPoints);\n  }\n  context.restore();\n\n}\n\nfunction drawXAxis(categories, opts, config, context) {\n\n  let xAxisData = opts.chartData.xAxisData,\n    xAxisPoints = xAxisData.xAxisPoints,\n    startX = xAxisData.startX,\n    endX = xAxisData.endX,\n    eachSpacing = xAxisData.eachSpacing;\n  var boundaryGap = 'center';\n  if (opts.type == 'bar' || opts.type == 'line' || opts.type == 'area'|| opts.type == 'scatter' || opts.type == 'bubble') {\n    boundaryGap = opts.xAxis.boundaryGap;\n  }\n  var startY = opts.height - opts.area[2];\n  var endY = opts.area[0];\n\n  //绘制滚动条\n  if (opts.enableScroll && opts.xAxis.scrollShow) {\n    var scrollY = opts.height - opts.area[2] + config.xAxisHeight;\n    var scrollScreenWidth = endX - startX;\n    var scrollTotalWidth = eachSpacing * (xAxisPoints.length - 1);\n    if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1){\n      if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2\n      scrollTotalWidth += (opts.extra.mount.widthRatio - 1)*eachSpacing;\n    }\n    var scrollWidth = scrollScreenWidth * scrollScreenWidth / scrollTotalWidth;\n    var scrollLeft = 0;\n    if (opts._scrollDistance_) {\n      scrollLeft = -opts._scrollDistance_ * (scrollScreenWidth) / scrollTotalWidth;\n    }\n    context.beginPath();\n    context.setLineCap('round');\n    context.setLineWidth(6 * opts.pix);\n    context.setStrokeStyle(opts.xAxis.scrollBackgroundColor || \"#EFEBEF\");\n    context.moveTo(startX, scrollY);\n    context.lineTo(endX, scrollY);\n    context.stroke();\n    context.closePath();\n    context.beginPath();\n    context.setLineCap('round');\n    context.setLineWidth(6 * opts.pix);\n    context.setStrokeStyle(opts.xAxis.scrollColor || \"#A6A6A6\");\n    context.moveTo(startX + scrollLeft, scrollY);\n    context.lineTo(startX + scrollLeft + scrollWidth, scrollY);\n    context.stroke();\n    context.closePath();\n    context.setLineCap('butt');\n  }\n  context.save();\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {\n    context.translate(opts._scrollDistance_, 0);\n  }\n  //绘制X轴刻度线\n  if (opts.xAxis.calibration === true) {\n    context.setStrokeStyle(opts.xAxis.gridColor || \"#cccccc\");\n    context.setLineCap('butt');\n    context.setLineWidth(1 * opts.pix);\n    xAxisPoints.forEach(function(item, index) {\n      if (index > 0) {\n        context.beginPath();\n        context.moveTo(item - eachSpacing / 2, startY);\n        context.lineTo(item - eachSpacing / 2, startY + 3 * opts.pix);\n        context.closePath();\n        context.stroke();\n      }\n    });\n  }\n  //绘制X轴网格\n  if (opts.xAxis.disableGrid !== true) {\n    context.setStrokeStyle(opts.xAxis.gridColor || \"#cccccc\");\n    context.setLineCap('butt');\n    context.setLineWidth(1 * opts.pix);\n    if (opts.xAxis.gridType == 'dash') {\n      context.setLineDash([opts.xAxis.dashLength * opts.pix, opts.xAxis.dashLength * opts.pix]);\n    }\n    opts.xAxis.gridEval = opts.xAxis.gridEval || 1;\n    xAxisPoints.forEach(function(item, index) {\n      if (index % opts.xAxis.gridEval == 0) {\n        context.beginPath();\n        context.moveTo(item, startY);\n        context.lineTo(item, endY);\n        context.stroke();\n      }\n    });\n    context.setLineDash([]);\n  }\n  //绘制X轴文案\n  if (opts.xAxis.disabled !== true) {\n    // 对X轴列表做抽稀处理\n    //默认全部显示X轴标签\n    let maxXAxisListLength = categories.length;\n    //如果设置了X轴单屏数量\n    if (opts.xAxis.labelCount) {\n      //如果设置X轴密度\n      if (opts.xAxis.itemCount) {\n        maxXAxisListLength = Math.ceil(categories.length / opts.xAxis.itemCount * opts.xAxis.labelCount);\n      } else {\n        maxXAxisListLength = opts.xAxis.labelCount;\n      }\n      maxXAxisListLength -= 1;\n    }\n\n    let ratio = Math.ceil(categories.length / maxXAxisListLength);\n\n    let newCategories = [];\n    let cgLength = categories.length;\n    for (let i = 0; i < cgLength; i++) {\n      if (i % ratio !== 0) {\n        newCategories.push(\"\");\n      } else {\n        newCategories.push(categories[i]);\n      }\n    }\n    newCategories[cgLength - 1] = categories[cgLength - 1];\n    var xAxisFontSize = opts.xAxis.fontSize * opts.pix || config.fontSize;\n    if (config._xAxisTextAngle_ === 0) {\n      newCategories.forEach(function(item, index) {\n        var xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item,index,opts) : item;\n        var offset = -measureText(String(xitem), xAxisFontSize, context) / 2;\n        if (boundaryGap == 'center') {\n          offset += eachSpacing / 2;\n        }\n        var scrollHeight = 0;\n        if (opts.xAxis.scrollShow) {\n          scrollHeight = 6 * opts.pix;\n        }\n        context.beginPath();\n        context.setFontSize(xAxisFontSize);\n        context.setFillStyle(opts.xAxis.fontColor || opts.fontColor);\n        context.fillText(String(xitem), xAxisPoints[index] + offset, startY + xAxisFontSize + (config.xAxisHeight - scrollHeight - xAxisFontSize) / 2);\n        context.closePath();\n        context.stroke();\n      });\n    } else {\n      newCategories.forEach(function(item, index) {\n        var xitem = opts.xAxis.formatter ? opts.xAxis.formatter(item) : item;\n        context.save();\n        context.beginPath();\n        context.setFontSize(xAxisFontSize);\n        context.setFillStyle(opts.xAxis.fontColor || opts.fontColor);\n        var textWidth = measureText(String(xitem), xAxisFontSize, context);\n        var offsetX = xAxisPoints[index];\n        if (boundaryGap == 'center') {\n          offsetX = xAxisPoints[index] + eachSpacing / 2;\n        }\n        var scrollHeight = 0;\n        if (opts.xAxis.scrollShow) {\n          scrollHeight = 6 * opts.pix;\n        }\n        var offsetY = startY + 6 * opts.pix + xAxisFontSize - xAxisFontSize * Math.abs(Math.sin(config._xAxisTextAngle_));\n        if(opts.xAxis.rotateAngle < 0){\n          offsetX -= xAxisFontSize / 2;\n          textWidth = 0;\n        }else{\n          offsetX += xAxisFontSize / 2;\n          textWidth = -textWidth;\n        }\n        context.translate(offsetX, offsetY);\n        context.rotate(-1 * config._xAxisTextAngle_);\n        context.fillText(String(xitem), textWidth , 0 );\n        context.closePath();\n        context.stroke();\n        context.restore();\n      });\n    }\n  }\n  context.restore();\n  //绘制X轴轴线\n  if (opts.xAxis.axisLine) {\n    context.beginPath();\n    context.setStrokeStyle(opts.xAxis.axisLineColor);\n    context.setLineWidth(1 * opts.pix);\n    context.moveTo(startX, opts.height - opts.area[2]);\n    context.lineTo(endX, opts.height - opts.area[2]);\n    context.stroke();\n  }\n}\n\nfunction drawYAxisGrid(categories, opts, config, context) {\n  if (opts.yAxis.disableGrid === true) {\n    return;\n  }\n  let spacingValid = opts.height - opts.area[0] - opts.area[2];\n  let eachSpacing = spacingValid / opts.yAxis.splitNumber;\n  let startX = opts.area[3];\n  let xAxisPoints = opts.chartData.xAxisData.xAxisPoints,\n    xAxiseachSpacing = opts.chartData.xAxisData.eachSpacing;\n  let TotalWidth = xAxiseachSpacing * (xAxisPoints.length - 1);\n  if(opts.type == 'mount' && opts.extra && opts.extra.mount && opts.extra.mount.widthRatio && opts.extra.mount.widthRatio > 1 ){\n    if(opts.extra.mount.widthRatio>2) opts.extra.mount.widthRatio = 2\n    TotalWidth += (opts.extra.mount.widthRatio - 1)*xAxiseachSpacing;\n  }\n  let endX = startX + TotalWidth;\n  let points = [];\n  let startY = 1\n  if (opts.xAxis.axisLine === false) {\n    startY = 0\n  }\n  for (let i = startY; i < opts.yAxis.splitNumber + 1; i++) {\n    points.push(opts.height - opts.area[2] - eachSpacing * i);\n  }\n  context.save();\n  if (opts._scrollDistance_ && opts._scrollDistance_ !== 0) {\n    context.translate(opts._scrollDistance_, 0);\n  }\n  if (opts.yAxis.gridType == 'dash') {\n    context.setLineDash([opts.yAxis.dashLength * opts.pix, opts.yAxis.dashLength * opts.pix]);\n  }\n  context.setStrokeStyle(opts.yAxis.gridColor);\n  context.setLineWidth(1 * opts.pix);\n  points.forEach(function(item, index) {\n    context.beginPath();\n    context.moveTo(startX, item);\n    context.lineTo(endX, item);\n    context.stroke();\n  });\n  context.setLineDash([]);\n  context.restore();\n}\n\nfunction drawYAxis(series, opts, config, context) {\n  if (opts.yAxis.disabled === true) {\n    return;\n  }\n  var spacingValid = opts.height - opts.area[0] - opts.area[2];\n  var eachSpacing = spacingValid / opts.yAxis.splitNumber;\n  var startX = opts.area[3];\n  var endX = opts.width - opts.area[1];\n  var endY = opts.height - opts.area[2];\n  var fillEndY = endY + config.xAxisHeight;\n  if (opts.xAxis.scrollShow) {\n    fillEndY -= 3 * opts.pix;\n  }\n  if (opts.xAxis.rotateLabel) {\n    fillEndY = opts.height - opts.area[2] + opts.fontSize * opts.pix / 2;\n  }\n  // set YAxis background\n  context.beginPath();\n  context.setFillStyle(opts.background);\n  if (opts.enableScroll == true && opts.xAxis.scrollPosition && opts.xAxis.scrollPosition !== 'left') {\n    context.fillRect(0, 0, startX, fillEndY);\n  }\n  if (opts.enableScroll == true && opts.xAxis.scrollPosition && opts.xAxis.scrollPosition !== 'right') {\n    context.fillRect(endX, 0, opts.width, fillEndY);\n  }\n  context.closePath();\n  context.stroke();\n  \n  let tStartLeft = opts.area[3];\n  let tStartRight = opts.width - opts.area[1];\n  let tStartCenter = opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2;\n  if (opts.yAxis.data) {\n    for (let i = 0; i < opts.yAxis.data.length; i++) {\n      let yData = opts.yAxis.data[i];\n      var points = [];\n      if(yData.type === 'categories'){\n        for (let i = 0; i <= yData.categories.length; i++) {\n          points.push(opts.area[0] + spacingValid / yData.categories.length / 2 + spacingValid / yData.categories.length * i);\n        }\n      }else{\n        for (let i = 0; i <= opts.yAxis.splitNumber; i++) {\n          points.push(opts.area[0] + eachSpacing * i);\n        }\n      }\n      if (yData.disabled !== true) {\n        let rangesFormat = opts.chartData.yAxisData.rangesFormat[i];\n        let yAxisFontSize = yData.fontSize ? yData.fontSize * opts.pix : config.fontSize;\n        let yAxisWidth = opts.chartData.yAxisData.yAxisWidth[i];\n        let textAlign = yData.textAlign || \"right\";\n        //画Y轴刻度及文案\n        rangesFormat.forEach(function(item, index) {\n          var pos = points[index];\n          context.beginPath();\n          context.setFontSize(yAxisFontSize);\n          context.setLineWidth(1 * opts.pix);\n          context.setStrokeStyle(yData.axisLineColor || '#cccccc');\n          context.setFillStyle(yData.fontColor || opts.fontColor);\n          let tmpstrat = 0;\n          let gapwidth = 4 * opts.pix;\n          if (yAxisWidth.position == 'left') {\n            //画刻度线\n            if (yData.calibration == true) {\n              context.moveTo(tStartLeft, pos);\n              context.lineTo(tStartLeft - 3 * opts.pix, pos);\n              gapwidth += 3 * opts.pix;\n            }\n            //画文字\n            switch (textAlign) {\n              case \"left\":\n                context.setTextAlign('left');\n                tmpstrat = tStartLeft - yAxisWidth.width\n                break;\n              case \"right\":\n                context.setTextAlign('right');\n                tmpstrat = tStartLeft - gapwidth\n                break;\n              default:\n                context.setTextAlign('center');\n                tmpstrat = tStartLeft - yAxisWidth.width / 2\n            }\n            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);\n\n          } else if (yAxisWidth.position == 'right') {\n            //画刻度线\n            if (yData.calibration == true) {\n              context.moveTo(tStartRight, pos);\n              context.lineTo(tStartRight + 3 * opts.pix, pos);\n              gapwidth += 3 * opts.pix;\n            }\n            switch (textAlign) {\n              case \"left\":\n                context.setTextAlign('left');\n                tmpstrat = tStartRight + gapwidth\n                break;\n              case \"right\":\n                context.setTextAlign('right');\n                tmpstrat = tStartRight + yAxisWidth.width\n                break;\n              default:\n                context.setTextAlign('center');\n                tmpstrat = tStartRight + yAxisWidth.width / 2\n            }\n            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);\n          } else if (yAxisWidth.position == 'center') {\n            //画刻度线\n            if (yData.calibration == true) {\n              context.moveTo(tStartCenter, pos);\n              context.lineTo(tStartCenter - 3 * opts.pix, pos);\n              gapwidth += 3 * opts.pix;\n            }\n            //画文字\n            switch (textAlign) {\n              case \"left\":\n                context.setTextAlign('left');\n                tmpstrat = tStartCenter - yAxisWidth.width\n                break;\n              case \"right\":\n                context.setTextAlign('right');\n                tmpstrat = tStartCenter - gapwidth\n                break;\n              default:\n                context.setTextAlign('center');\n                tmpstrat = tStartCenter - yAxisWidth.width / 2\n            }\n            context.fillText(String(item), tmpstrat, pos + yAxisFontSize / 2 - 3 * opts.pix);\n          }\n          context.closePath();\n          context.stroke();\n          context.setTextAlign('left');\n        });\n        //画Y轴轴线\n        if (yData.axisLine !== false) {\n          context.beginPath();\n          context.setStrokeStyle(yData.axisLineColor || '#cccccc');\n          context.setLineWidth(1 * opts.pix);\n          if (yAxisWidth.position == 'left') {\n            context.moveTo(tStartLeft, opts.height - opts.area[2]);\n            context.lineTo(tStartLeft, opts.area[0]);\n          } else if (yAxisWidth.position == 'right') {\n            context.moveTo(tStartRight, opts.height - opts.area[2]);\n            context.lineTo(tStartRight, opts.area[0]);\n          } else if (yAxisWidth.position == 'center') {\n            context.moveTo(tStartCenter, opts.height - opts.area[2]);\n            context.lineTo(tStartCenter, opts.area[0]);\n          }\n          context.stroke();\n        }\n        //画Y轴标题\n        if (opts.yAxis.showTitle) {\n          let titleFontSize = yData.titleFontSize * opts.pix || config.fontSize;\n          let title = yData.title;\n          context.beginPath();\n          context.setFontSize(titleFontSize);\n          context.setFillStyle(yData.titleFontColor || opts.fontColor);\n          if (yAxisWidth.position == 'left') {\n            context.fillText(title, tStartLeft - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);\n          } else if (yAxisWidth.position == 'right') {\n            context.fillText(title, tStartRight - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);\n          } else if (yAxisWidth.position == 'center') {\n            context.fillText(title, tStartCenter - measureText(title, titleFontSize, context) / 2 + (yData.titleOffsetX || 0), opts.area[0] - (10 - (yData.titleOffsetY || 0)) * opts.pix);\n          }\n          context.closePath();\n          context.stroke();\n        }\n        if (yAxisWidth.position == 'left') {\n          tStartLeft -= (yAxisWidth.width + opts.yAxis.padding * opts.pix);\n        } else {\n          tStartRight += yAxisWidth.width + opts.yAxis.padding * opts.pix;\n        }\n      }\n    }\n  }\n\n}\n\nfunction drawLegend(series, opts, config, context, chartData) {\n  if (opts.legend.show === false) {\n    return;\n  }\n  let legendData = chartData.legendData;\n  let legendList = legendData.points;\n  let legendArea = legendData.area;\n  let padding = opts.legend.padding * opts.pix;\n  let fontSize = opts.legend.fontSize * opts.pix;\n  let shapeWidth = 15 * opts.pix;\n  let shapeRight = 5 * opts.pix;\n  let itemGap = opts.legend.itemGap * opts.pix;\n  let lineHeight = Math.max(opts.legend.lineHeight * opts.pix, fontSize);\n  //画背景及边框\n  context.beginPath();\n  context.setLineWidth(opts.legend.borderWidth * opts.pix);\n  context.setStrokeStyle(opts.legend.borderColor);\n  context.setFillStyle(opts.legend.backgroundColor);\n  context.moveTo(legendArea.start.x, legendArea.start.y);\n  context.rect(legendArea.start.x, legendArea.start.y, legendArea.width, legendArea.height);\n  context.closePath();\n  context.fill();\n  context.stroke();\n  legendList.forEach(function(itemList, listIndex) {\n    let width = 0;\n    let height = 0;\n    width = legendData.widthArr[listIndex];\n    height = legendData.heightArr[listIndex];\n    let startX = 0;\n    let startY = 0;\n    if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {\n      switch (opts.legend.float) {\n        case 'left':\n          startX = legendArea.start.x + padding;\n        break;\n        case 'right':\n          startX = legendArea.start.x + legendArea.width - width;\n        break;\n        default:\n        startX = legendArea.start.x + (legendArea.width - width) / 2;\n      }\n      startY = legendArea.start.y + padding + listIndex * lineHeight;\n    } else {\n      if (listIndex == 0) {\n        width = 0;\n      } else {\n        width = legendData.widthArr[listIndex - 1];\n      }\n      startX = legendArea.start.x + padding + width;\n      startY = legendArea.start.y + padding + (legendArea.height - height) / 2;\n    }\n    context.setFontSize(config.fontSize);\n    for (let i = 0; i < itemList.length; i++) {\n      let item = itemList[i];\n      item.area = [0, 0, 0, 0];\n      item.area[0] = startX;\n      item.area[1] = startY;\n      item.area[3] = startY + lineHeight;\n      context.beginPath();\n      context.setLineWidth(1 * opts.pix);\n      context.setStrokeStyle(item.show ? item.color : opts.legend.hiddenColor);\n      context.setFillStyle(item.show ? item.color : opts.legend.hiddenColor);\n      switch (item.legendShape) {\n        case 'line':\n          context.moveTo(startX, startY + 0.5 * lineHeight - 2 * opts.pix);\n          context.fillRect(startX, startY + 0.5 * lineHeight - 2 * opts.pix, 15 * opts.pix, 4 * opts.pix);\n          break;\n        case 'triangle':\n          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);\n          context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);\n          context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);\n          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);\n          break;\n        case 'diamond':\n          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);\n          context.lineTo(startX + 2.5 * opts.pix, startY + 0.5 * lineHeight);\n          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight + 5 * opts.pix);\n          context.lineTo(startX + 12.5 * opts.pix, startY + 0.5 * lineHeight);\n          context.lineTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);\n          break;\n        case 'circle':\n          context.moveTo(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight);\n          context.arc(startX + 7.5 * opts.pix, startY + 0.5 * lineHeight, 5 * opts.pix, 0, 2 * Math.PI);\n          break;\n        case 'rect':\n          context.moveTo(startX, startY + 0.5 * lineHeight - 5 * opts.pix);\n          context.fillRect(startX, startY + 0.5 * lineHeight - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);\n          break;\n        case 'square':\n          context.moveTo(startX + 5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix);\n          context.fillRect(startX + 5 * opts.pix, startY + 0.5 * lineHeight - 5 * opts.pix, 10 * opts.pix, 10 * opts.pix);\n          break;\n        case 'none':\n          break;\n        default:\n          context.moveTo(startX, startY + 0.5 * lineHeight - 5 * opts.pix);\n          context.fillRect(startX, startY + 0.5 * lineHeight - 5 * opts.pix, 15 * opts.pix, 10 * opts.pix);\n      }\n      context.closePath();\n      context.fill();\n      context.stroke();\n      startX += shapeWidth + shapeRight;\n      let fontTrans = 0.5 * lineHeight + 0.5 * fontSize - 2;\n      const legendText = item.legendText ? item.legendText : item.name;\n      context.beginPath();\n      context.setFontSize(fontSize);\n      context.setFillStyle(item.show ? opts.legend.fontColor : opts.legend.hiddenColor);\n      context.fillText(legendText, startX, startY + fontTrans);\n      context.closePath();\n      context.stroke();\n      if (opts.legend.position == 'top' || opts.legend.position == 'bottom') {\n        startX += measureText(legendText, fontSize, context) + itemGap;\n        item.area[2] = startX;\n      } else {\n        item.area[2] = startX + measureText(legendText, fontSize, context) + itemGap;;\n        startX -= shapeWidth + shapeRight;\n        startY += lineHeight;\n      }\n    }\n  });\n}\n\nfunction drawPieDataPoints(series, opts, config, context) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var pieOption = assign({}, {\n    activeOpacity: 0.5,\n    activeRadius: 10,\n    offsetAngle: 0,\n    labelWidth: 15,\n    ringWidth: 30,\n    customRadius: 0,\n    border: false,\n    borderWidth: 2,\n    borderColor: '#FFFFFF',\n    centerColor: '#FFFFFF',\n    linearType: 'none',\n    customColor: [],\n  }, opts.type == \"pie\" ? opts.extra.pie : opts.extra.ring);\n  var centerPosition = {\n    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,\n    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2\n  };\n  if (config.pieChartLinePadding == 0) {\n    config.pieChartLinePadding = pieOption.activeRadius * opts.pix;\n  }\n\n  var radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, (opts.height - opts.area[0] - opts.area[2]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding);\n  radius = radius < 10 ? 10 : radius;\n  if (pieOption.customRadius > 0) {\n    radius = pieOption.customRadius * opts.pix;\n  }\n  series = getPieDataPoints(series, radius, process);\n  var activeRadius = pieOption.activeRadius * opts.pix;\n  pieOption.customColor = fillCustomColor(pieOption.linearType, pieOption.customColor, series, config);\n  series = series.map(function(eachSeries) {\n    eachSeries._start_ += (pieOption.offsetAngle) * Math.PI / 180;\n    return eachSeries;\n  });\n  series.forEach(function(eachSeries, seriesIndex) {\n    if (opts.tooltip) {\n      if (opts.tooltip.index == seriesIndex) {\n        context.beginPath();\n        context.setFillStyle(hexToRgb(eachSeries.color, pieOption.activeOpacity || 0.5));\n        context.moveTo(centerPosition.x, centerPosition.y);\n        context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_ + activeRadius, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._proportion_ * Math.PI);\n        context.closePath();\n        context.fill();\n      }\n    }\n    context.beginPath();\n    context.setLineWidth(pieOption.borderWidth * opts.pix);\n    context.lineJoin = \"round\";\n    context.setStrokeStyle(pieOption.borderColor);\n    var fillcolor = eachSeries.color;\n    if (pieOption.linearType == 'custom') {\n      var grd;\n      if(context.createCircularGradient){\n        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, eachSeries._radius_)\n      }else{\n        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, eachSeries._radius_)\n      }\n      grd.addColorStop(0, hexToRgb(pieOption.customColor[eachSeries.linearIndex], 1))\n      grd.addColorStop(1, hexToRgb(eachSeries.color, 1))\n      fillcolor = grd\n    }\n    context.setFillStyle(fillcolor);\n    context.moveTo(centerPosition.x, centerPosition.y);\n    context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._proportion_ * Math.PI);\n    context.closePath();\n    context.fill();\n    if (pieOption.border == true) {\n      context.stroke();\n    }\n  });\n  if (opts.type === 'ring') {\n    var innerPieWidth = radius * 0.6;\n    if (typeof pieOption.ringWidth === 'number' && pieOption.ringWidth > 0) {\n      innerPieWidth = Math.max(0, radius - pieOption.ringWidth * opts.pix);\n    }\n    context.beginPath();\n    context.setFillStyle(pieOption.centerColor);\n    context.moveTo(centerPosition.x, centerPosition.y);\n    context.arc(centerPosition.x, centerPosition.y, innerPieWidth, 0, 2 * Math.PI);\n    context.closePath();\n    context.fill();\n  }\n  if (opts.dataLabel !== false && process === 1) {\n    drawPieText(series, opts, config, context, radius, centerPosition);\n  }\n  if (process === 1 && opts.type === 'ring') {\n    drawRingTitle(opts, config, context, centerPosition);\n  }\n  return {\n    center: centerPosition,\n    radius: radius,\n    series: series\n  };\n}\n\nfunction drawRoseDataPoints(series, opts, config, context) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var roseOption = assign({}, {\n    type: 'area',\n    activeOpacity: 0.5,\n    activeRadius: 10,\n    offsetAngle: 0,\n    labelWidth: 15,\n    border: false,\n    borderWidth: 2,\n    borderColor: '#FFFFFF',\n    linearType: 'none',\n    customColor: [],\n  }, opts.extra.rose);\n  if (config.pieChartLinePadding == 0) {\n    config.pieChartLinePadding = roseOption.activeRadius * opts.pix;\n  }\n  var centerPosition = {\n    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,\n    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2\n  };\n  var radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding - config._pieTextMaxLength_, (opts.height - opts.area[0] - opts.area[2]) / 2 - config.pieChartLinePadding - config.pieChartTextPadding);\n  radius = radius < 10 ? 10 : radius;\n  var minRadius = roseOption.minRadius || radius * 0.5;\n  series = getRoseDataPoints(series, roseOption.type, minRadius, radius, process);\n  var activeRadius = roseOption.activeRadius * opts.pix;\n  roseOption.customColor = fillCustomColor(roseOption.linearType, roseOption.customColor, series, config);\n  series = series.map(function(eachSeries) {\n    eachSeries._start_ += (roseOption.offsetAngle || 0) * Math.PI / 180;\n    return eachSeries;\n  });\n  series.forEach(function(eachSeries, seriesIndex) {\n    if (opts.tooltip) {\n      if (opts.tooltip.index == seriesIndex) {\n        context.beginPath();\n        context.setFillStyle(hexToRgb(eachSeries.color, roseOption.activeOpacity || 0.5));\n        context.moveTo(centerPosition.x, centerPosition.y);\n        context.arc(centerPosition.x, centerPosition.y, activeRadius + eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._rose_proportion_ * Math.PI);\n        context.closePath();\n        context.fill();\n      }\n    }\n    context.beginPath();\n    context.setLineWidth(roseOption.borderWidth * opts.pix);\n    context.lineJoin = \"round\";\n    context.setStrokeStyle(roseOption.borderColor);\n    var fillcolor = eachSeries.color;\n    if (roseOption.linearType == 'custom') {\n      var grd;\n      if(context.createCircularGradient){\n        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, eachSeries._radius_)\n      }else{\n        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, eachSeries._radius_)\n      }\n      grd.addColorStop(0, hexToRgb(roseOption.customColor[eachSeries.linearIndex], 1))\n      grd.addColorStop(1, hexToRgb(eachSeries.color, 1))\n      fillcolor = grd\n    }\n    context.setFillStyle(fillcolor);\n    context.moveTo(centerPosition.x, centerPosition.y);\n    context.arc(centerPosition.x, centerPosition.y, eachSeries._radius_, eachSeries._start_, eachSeries._start_ + 2 * eachSeries._rose_proportion_ * Math.PI);\n    context.closePath();\n    context.fill();\n    if (roseOption.border == true) {\n      context.stroke();\n    }\n  });\n\n  if (opts.dataLabel !== false && process === 1) {\n    drawPieText(series, opts, config, context, radius, centerPosition);\n  }\n  return {\n    center: centerPosition,\n    radius: radius,\n    series: series\n  };\n}\n\nfunction drawArcbarDataPoints(series, opts, config, context) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var arcbarOption = assign({}, {\n    startAngle: 0.75,\n    endAngle: 0.25,\n    type: 'default',\n    lineCap: 'round',\n    width: 12 ,\n    gap: 2 ,\n    linearType: 'none',\n    customColor: [],\n  }, opts.extra.arcbar);\n  series = getArcbarDataPoints(series, arcbarOption, process);\n  var centerPosition;\n  if (arcbarOption.centerX || arcbarOption.centerY) {\n    centerPosition = {\n      x: arcbarOption.centerX ? arcbarOption.centerX : opts.width / 2,\n      y: arcbarOption.centerY ? arcbarOption.centerY : opts.height / 2\n    };\n  } else {\n    centerPosition = {\n      x: opts.width / 2,\n      y: opts.height / 2\n    };\n  }\n  var radius;\n  if (arcbarOption.radius) {\n    radius = arcbarOption.radius;\n  } else {\n    radius = Math.min(centerPosition.x, centerPosition.y);\n    radius -= 5 * opts.pix;\n    radius -= arcbarOption.width / 2;\n  }\n  radius = radius < 10 ? 10 : radius;\n  arcbarOption.customColor = fillCustomColor(arcbarOption.linearType, arcbarOption.customColor, series, config);\n  \n  for (let i = 0; i < series.length; i++) {\n    let eachSeries = series[i];\n    //背景颜色\n    context.setLineWidth(arcbarOption.width * opts.pix);\n    context.setStrokeStyle(arcbarOption.backgroundColor || '#E9E9E9');\n    context.setLineCap(arcbarOption.lineCap);\n    context.beginPath();\n    if (arcbarOption.type == 'default') {\n      context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, arcbarOption.startAngle * Math.PI, arcbarOption.endAngle * Math.PI, false);\n    } else {\n      context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, 0, 2 * Math.PI, false);\n    }\n    context.stroke();\n    //进度条\n    var fillColor = eachSeries.color\n    if(arcbarOption.linearType == 'custom'){\n      var grd = context.createLinearGradient(centerPosition.x - radius, centerPosition.y, centerPosition.x + radius, centerPosition.y);\n      grd.addColorStop(1, hexToRgb(arcbarOption.customColor[eachSeries.linearIndex], 1))\n      grd.addColorStop(0, hexToRgb(eachSeries.color, 1))\n      fillColor = grd;\n    }\n    context.setLineWidth(arcbarOption.width * opts.pix);\n    context.setStrokeStyle(fillColor);\n    context.setLineCap(arcbarOption.lineCap);\n    context.beginPath();\n    context.arc(centerPosition.x, centerPosition.y, radius - (arcbarOption.width * opts.pix + arcbarOption.gap * opts.pix) * i, arcbarOption.startAngle * Math.PI, eachSeries._proportion_ * Math.PI, false);\n    context.stroke();\n  }\n  drawRingTitle(opts, config, context, centerPosition);\n  return {\n    center: centerPosition,\n    radius: radius,\n    series: series\n  };\n}\n\nfunction drawGaugeDataPoints(categories, series, opts, config, context) {\n  var process = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;\n  var gaugeOption = assign({}, {\n    type: 'default',\n    startAngle: 0.75,\n    endAngle: 0.25,\n    width: 15,\n    labelOffset:13,\n    splitLine: {\n      fixRadius: 0,\n      splitNumber: 10,\n      width: 15,\n      color: '#FFFFFF',\n      childNumber: 5,\n      childWidth: 5\n    },\n    pointer: {\n      width: 15,\n      color: 'auto'\n    }\n  }, opts.extra.gauge);\n  if (gaugeOption.oldAngle == undefined) {\n    gaugeOption.oldAngle = gaugeOption.startAngle;\n  }\n  if (gaugeOption.oldData == undefined) {\n    gaugeOption.oldData = 0;\n  }\n  categories = getGaugeAxisPoints(categories, gaugeOption.startAngle, gaugeOption.endAngle);\n  var centerPosition = {\n    x: opts.width / 2,\n    y: opts.height / 2\n  };\n  var radius = Math.min(centerPosition.x, centerPosition.y);\n  radius -= 5 * opts.pix;\n  radius -= gaugeOption.width / 2;\n  radius = radius < 10 ? 10 : radius;\n  var innerRadius = radius - gaugeOption.width;\n  var totalAngle = 0;\n  //判断仪表盘的样式：default百度样式，progress新样式\n  if (gaugeOption.type == 'progress') {\n    //## 第一步画中心圆形背景和进度条背景\n    //中心圆形背景\n    var pieRadius = radius - gaugeOption.width * 3;\n    context.beginPath();\n    let gradient = context.createLinearGradient(centerPosition.x, centerPosition.y - pieRadius, centerPosition.x, centerPosition.y + pieRadius);\n    //配置渐变填充（起点：中心点向上减半径；结束点中心点向下加半径）\n    gradient.addColorStop('0', hexToRgb(series[0].color, 0.3));\n    gradient.addColorStop('1.0', hexToRgb(\"#FFFFFF\", 0.1));\n    context.setFillStyle(gradient);\n    context.arc(centerPosition.x, centerPosition.y, pieRadius, 0, 2 * Math.PI, false);\n    context.fill();\n    //画进度条背景\n    context.setLineWidth(gaugeOption.width);\n    context.setStrokeStyle(hexToRgb(series[0].color, 0.3));\n    context.setLineCap('round');\n    context.beginPath();\n    context.arc(centerPosition.x, centerPosition.y, innerRadius, gaugeOption.startAngle * Math.PI, gaugeOption.endAngle * Math.PI, false);\n    context.stroke();\n    //## 第二步画刻度线\n    totalAngle = gaugeOption.startAngle - gaugeOption.endAngle + 1;\n    let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;\n    let childAngle = totalAngle / gaugeOption.splitLine.splitNumber / gaugeOption.splitLine.childNumber;\n    let startX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius;\n    let endX = -radius - gaugeOption.width - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.width;\n    context.save();\n    context.translate(centerPosition.x, centerPosition.y);\n    context.rotate((gaugeOption.startAngle - 1) * Math.PI);\n    let len = gaugeOption.splitLine.splitNumber * gaugeOption.splitLine.childNumber + 1;\n    let proc = series[0].data * process;\n    for (let i = 0; i < len; i++) {\n      context.beginPath();\n      //刻度线随进度变色\n      if (proc > (i / len)) {\n        context.setStrokeStyle(hexToRgb(series[0].color, 1));\n      } else {\n        context.setStrokeStyle(hexToRgb(series[0].color, 0.3));\n      }\n      context.setLineWidth(3 * opts.pix);\n      context.moveTo(startX, 0);\n      context.lineTo(endX, 0);\n      context.stroke();\n      context.rotate(childAngle * Math.PI);\n    }\n    context.restore();\n    //## 第三步画进度条\n    series = getGaugeArcbarDataPoints(series, gaugeOption, process);\n    context.setLineWidth(gaugeOption.width);\n    context.setStrokeStyle(series[0].color);\n    context.setLineCap('round');\n    context.beginPath();\n    context.arc(centerPosition.x, centerPosition.y, innerRadius, gaugeOption.startAngle * Math.PI, series[0]._proportion_ * Math.PI, false);\n    context.stroke();\n    //## 第四步画指针\n    let pointerRadius = radius - gaugeOption.width * 2.5;\n    context.save();\n    context.translate(centerPosition.x, centerPosition.y);\n    context.rotate((series[0]._proportion_ - 1) * Math.PI);\n    context.beginPath();\n    context.setLineWidth(gaugeOption.width / 3);\n    let gradient3 = context.createLinearGradient(0, -pointerRadius * 0.6, 0, pointerRadius * 0.6);\n    gradient3.addColorStop('0', hexToRgb('#FFFFFF', 0));\n    gradient3.addColorStop('0.5', hexToRgb(series[0].color, 1));\n    gradient3.addColorStop('1.0', hexToRgb('#FFFFFF', 0));\n    context.setStrokeStyle(gradient3);\n    context.arc(0, 0, pointerRadius, 0.85 * Math.PI, 1.15 * Math.PI, false);\n    context.stroke();\n    context.beginPath();\n    context.setLineWidth(1);\n    context.setStrokeStyle(series[0].color);\n    context.setFillStyle(series[0].color);\n    context.moveTo(-pointerRadius - gaugeOption.width / 3 / 2, -4);\n    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2 - 4, 0);\n    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2, 4);\n    context.lineTo(-pointerRadius - gaugeOption.width / 3 / 2, -4);\n    context.stroke();\n    context.fill();\n    context.restore();\n    //default百度样式\n  } else {\n    //画背景\n    context.setLineWidth(gaugeOption.width);\n    context.setLineCap('butt');\n    for (let i = 0; i < categories.length; i++) {\n      let eachCategories = categories[i];\n      context.beginPath();\n      context.setStrokeStyle(eachCategories.color);\n      context.arc(centerPosition.x, centerPosition.y, radius, eachCategories._startAngle_ * Math.PI, eachCategories._endAngle_ * Math.PI, false);\n      context.stroke();\n    }\n    context.save();\n    //画刻度线\n    totalAngle = gaugeOption.startAngle - gaugeOption.endAngle + 1;\n    let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber;\n    let childAngle = totalAngle / gaugeOption.splitLine.splitNumber / gaugeOption.splitLine.childNumber;\n    let startX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius;\n    let endX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.width;\n    let childendX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.childWidth;\n    context.translate(centerPosition.x, centerPosition.y);\n    context.rotate((gaugeOption.startAngle - 1) * Math.PI);\n    for (let i = 0; i < gaugeOption.splitLine.splitNumber + 1; i++) {\n      context.beginPath();\n      context.setStrokeStyle(gaugeOption.splitLine.color);\n      context.setLineWidth(2 * opts.pix);\n      context.moveTo(startX, 0);\n      context.lineTo(endX, 0);\n      context.stroke();\n      context.rotate(splitAngle * Math.PI);\n    }\n    context.restore();\n    context.save();\n    context.translate(centerPosition.x, centerPosition.y);\n    context.rotate((gaugeOption.startAngle - 1) * Math.PI);\n    for (let i = 0; i < gaugeOption.splitLine.splitNumber * gaugeOption.splitLine.childNumber + 1; i++) {\n      context.beginPath();\n      context.setStrokeStyle(gaugeOption.splitLine.color);\n      context.setLineWidth(1 * opts.pix);\n      context.moveTo(startX, 0);\n      context.lineTo(childendX, 0);\n      context.stroke();\n      context.rotate(childAngle * Math.PI);\n    }\n    context.restore();\n    //画指针\n    series = getGaugeDataPoints(series, categories, gaugeOption, process);\n    for (let i = 0; i < series.length; i++) {\n      let eachSeries = series[i];\n      context.save();\n      context.translate(centerPosition.x, centerPosition.y);\n      context.rotate((eachSeries._proportion_ - 1) * Math.PI);\n      context.beginPath();\n      context.setFillStyle(eachSeries.color);\n      context.moveTo(gaugeOption.pointer.width, 0);\n      context.lineTo(0, -gaugeOption.pointer.width / 2);\n      context.lineTo(-innerRadius, 0);\n      context.lineTo(0, gaugeOption.pointer.width / 2);\n      context.lineTo(gaugeOption.pointer.width, 0);\n      context.closePath();\n      context.fill();\n      context.beginPath();\n      context.setFillStyle('#FFFFFF');\n      context.arc(0, 0, gaugeOption.pointer.width / 6, 0, 2 * Math.PI, false);\n      context.fill();\n      context.restore();\n    }\n    if (opts.dataLabel !== false) {\n      drawGaugeLabel(gaugeOption, radius, centerPosition, opts, config, context);\n    }\n  }\n  //画仪表盘标题，副标题\n  drawRingTitle(opts, config, context, centerPosition);\n  if (process === 1 && opts.type === 'gauge') {\n    opts.extra.gauge.oldAngle = series[0]._proportion_;\n    opts.extra.gauge.oldData = series[0].data;\n  }\n  return {\n    center: centerPosition,\n    radius: radius,\n    innerRadius: innerRadius,\n    categories: categories,\n    totalAngle: totalAngle\n  };\n}\n\nfunction drawRadarDataPoints(series, opts, config, context) {\n  var process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  var radarOption = assign({}, {\n    gridColor: '#cccccc',\n    gridType: 'radar',\n    gridEval:1,\n    axisLabel:false,\n    axisLabelTofix:0,\n    labelColor:'#666666',\n    labelPointShow:false,\n    labelPointRadius:3,\n    labelPointColor:'#cccccc',\n    opacity: 0.2,\n    gridCount: 3,\n    border:false,\n    borderWidth:2,\n    linearType: 'none',\n    customColor: [],\n  }, opts.extra.radar);\n  var coordinateAngle = getRadarCoordinateSeries(opts.categories.length);\n  var centerPosition = {\n    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,\n    y: opts.area[0] + (opts.height - opts.area[0] - opts.area[2]) / 2\n  };\n  var xr = (opts.width - opts.area[1] - opts.area[3]) / 2\n  var yr = (opts.height - opts.area[0] - opts.area[2]) / 2\n  var radius = Math.min(xr - (getMaxTextListLength(opts.categories, config.fontSize, context) + config.radarLabelTextMargin), yr - config.radarLabelTextMargin);\n  radius -= config.radarLabelTextMargin * opts.pix;\n  radius = radius < 10 ? 10 : radius;\n  // 画分割线\n  context.beginPath();\n  context.setLineWidth(1 * opts.pix);\n  context.setStrokeStyle(radarOption.gridColor);\n  coordinateAngle.forEach(function(angle,index) {\n    var pos = convertCoordinateOrigin(radius * Math.cos(angle), radius * Math.sin(angle), centerPosition);\n    context.moveTo(centerPosition.x, centerPosition.y);\n    if (index % radarOption.gridEval == 0) {\n      context.lineTo(pos.x, pos.y);\n    }\n  });\n  context.stroke();\n  context.closePath();\n  \n  // 画背景网格\n  var _loop = function _loop(i) {\n    var startPos = {};\n    context.beginPath();\n    context.setLineWidth(1 * opts.pix);\n    context.setStrokeStyle(radarOption.gridColor);\n    if (radarOption.gridType == 'radar') {\n      coordinateAngle.forEach(function(angle, index) {\n        var pos = convertCoordinateOrigin(radius / radarOption.gridCount * i * Math.cos(angle), radius /\n          radarOption.gridCount * i * Math.sin(angle), centerPosition);\n        if (index === 0) {\n          startPos = pos;\n          context.moveTo(pos.x, pos.y);\n        } else {\n          context.lineTo(pos.x, pos.y);\n        }\n      });\n      context.lineTo(startPos.x, startPos.y);\n    } else {\n      var pos = convertCoordinateOrigin(radius / radarOption.gridCount * i * Math.cos(1.5), radius / radarOption.gridCount * i * Math.sin(1.5), centerPosition);\n      context.arc(centerPosition.x, centerPosition.y, centerPosition.y - pos.y, 0, 2 * Math.PI, false);\n    }\n    context.stroke();\n    context.closePath();\n  };\n  for (var i = 1; i <= radarOption.gridCount; i++) {\n    _loop(i);\n  }\n  radarOption.customColor = fillCustomColor(radarOption.linearType, radarOption.customColor, series, config);\n  var radarDataPoints = getRadarDataPoints(coordinateAngle, centerPosition, radius, series, opts, process);\n  radarDataPoints.forEach(function(eachSeries, seriesIndex) {\n    // 绘制区域数据\n    context.beginPath();\n    context.setLineWidth(radarOption.borderWidth * opts.pix);\n    context.setStrokeStyle(eachSeries.color);\n    \n    var fillcolor = hexToRgb(eachSeries.color, radarOption.opacity);\n    if (radarOption.linearType == 'custom') {\n      var grd;\n      if(context.createCircularGradient){\n        grd = context.createCircularGradient(centerPosition.x, centerPosition.y, radius)\n      }else{\n        grd = context.createRadialGradient(centerPosition.x, centerPosition.y, 0,centerPosition.x, centerPosition.y, radius)\n      }\n      grd.addColorStop(0, hexToRgb(radarOption.customColor[series[seriesIndex].linearIndex], radarOption.opacity))\n      grd.addColorStop(1, hexToRgb(eachSeries.color, radarOption.opacity))\n      fillcolor = grd\n    }\n    \n    context.setFillStyle(fillcolor);\n    eachSeries.data.forEach(function(item, index) {\n      if (index === 0) {\n        context.moveTo(item.position.x, item.position.y);\n      } else {\n        context.lineTo(item.position.x, item.position.y);\n      }\n    });\n    context.closePath();\n    context.fill();\n    if(radarOption.border === true){\n      context.stroke();\n    }\n    context.closePath();\n    if (opts.dataPointShape !== false) {\n      var points = eachSeries.data.map(function(item) {\n        return item.position;\n      });\n      drawPointShape(points, eachSeries.color, eachSeries.pointShape, context, opts);\n    }\n  });\n  // 画刻度值\n  if(radarOption.axisLabel === true){\n    const maxData = Math.max(radarOption.max, Math.max.apply(null, dataCombine(series)));\n    const stepLength = radius / radarOption.gridCount;\n    const fontSize = opts.fontSize * opts.pix;\n    context.setFontSize(fontSize);\n    context.setFillStyle(opts.fontColor);\n    context.setTextAlign('left');\n    for (var i = 0; i < radarOption.gridCount + 1; i++) {\n      let label = i * maxData / radarOption.gridCount;\n      label = label.toFixed(radarOption.axisLabelTofix);\n      context.fillText(String(label), centerPosition.x + 3 * opts.pix, centerPosition.y - i * stepLength + fontSize / 2);\n    }\n  }\n  \n  // draw label text\n  drawRadarLabel(coordinateAngle, radius, centerPosition, opts, config, context);\n  \n  // draw dataLabel\n  if (opts.dataLabel !== false && process === 1) {\n    radarDataPoints.forEach(function(eachSeries, seriesIndex) {\n      context.beginPath();\n      var fontSize = eachSeries.textSize * opts.pix || config.fontSize;\n      context.setFontSize(fontSize);\n      context.setFillStyle(eachSeries.textColor || opts.fontColor);\n      eachSeries.data.forEach(function(item, index) {\n        //如果是中心点垂直的上下点位\n        if(Math.abs(item.position.x - centerPosition.x)<2){\n          //如果在上面\n          if(item.position.y < centerPosition.y){\n            context.setTextAlign('center');\n            context.fillText(item.value, item.position.x, item.position.y - 4);\n          }else{\n            context.setTextAlign('center');\n            context.fillText(item.value, item.position.x, item.position.y + fontSize + 2);\n          }\n        }else{\n          //如果在左侧\n          if(item.position.x < centerPosition.x){\n            context.setTextAlign('right');\n            context.fillText(item.value, item.position.x - 4, item.position.y + fontSize / 2 - 2);\n          }else{\n            context.setTextAlign('left');\n            context.fillText(item.value, item.position.x + 4, item.position.y + fontSize / 2 - 2);\n          }\n        }\n      });\n      context.closePath();\n      context.stroke();\n    });\n    context.setTextAlign('left');\n  }\n  \n  return {\n    center: centerPosition,\n    radius: radius,\n    angleList: coordinateAngle\n  };\n}\n\n// 经纬度转墨卡托\nfunction lonlat2mercator(longitude, latitude) {\n  var mercator = Array(2);\n  var x = longitude * 20037508.34 / 180;\n  var y = Math.log(Math.tan((90 + latitude) * Math.PI / 360)) / (Math.PI / 180);\n  y = y * 20037508.34 / 180;\n  mercator[0] = x;\n  mercator[1] = y;\n  return mercator;\n}\n\n// 墨卡托转经纬度\nfunction mercator2lonlat(longitude, latitude) {\n  var lonlat = Array(2)\n  var x = longitude / 20037508.34 * 180;\n  var y = latitude / 20037508.34 * 180;\n  y = 180 / Math.PI * (2 * Math.atan(Math.exp(y * Math.PI / 180)) - Math.PI / 2);\n  lonlat[0] = x;\n  lonlat[1] = y;\n  return lonlat;\n}\n\nfunction getBoundingBox(data) {\n  var bounds = {},coords;\n  bounds.xMin = 180;\n  bounds.xMax = 0;\n  bounds.yMin = 90;\n  bounds.yMax = 0\n  for (var i = 0; i < data.length; i++) {\n    var coorda = data[i].geometry.coordinates\n    for (var k = 0; k < coorda.length; k++) {\n      coords = coorda[k];\n      if (coords.length == 1) {\n        coords = coords[0]\n      }\n      for (var j = 0; j < coords.length; j++) {\n        var longitude = coords[j][0];\n        var latitude = coords[j][1];\n        var point = {\n          x: longitude,\n          y: latitude\n        }\n        bounds.xMin = bounds.xMin < point.x ? bounds.xMin : point.x;\n        bounds.xMax = bounds.xMax > point.x ? bounds.xMax : point.x;\n        bounds.yMin = bounds.yMin < point.y ? bounds.yMin : point.y;\n        bounds.yMax = bounds.yMax > point.y ? bounds.yMax : point.y;\n      }\n    }\n  }\n  return bounds;\n}\n\nfunction coordinateToPoint(latitude, longitude, bounds, scale, xoffset, yoffset) {\n  return {\n    x: (longitude - bounds.xMin) * scale + xoffset,\n    y: (bounds.yMax - latitude) * scale + yoffset\n  };\n}\n\nfunction pointToCoordinate(pointY, pointX, bounds, scale, xoffset, yoffset) {\n  return {\n    x: (pointX - xoffset) / scale + bounds.xMin,\n    y: bounds.yMax - (pointY - yoffset) / scale\n  };\n}\n\nfunction isRayIntersectsSegment(poi, s_poi, e_poi) {\n  if (s_poi[1] == e_poi[1]) {\n    return false;\n  }\n  if (s_poi[1] > poi[1] && e_poi[1] > poi[1]) {\n    return false;\n  }\n  if (s_poi[1] < poi[1] && e_poi[1] < poi[1]) {\n    return false;\n  }\n  if (s_poi[1] == poi[1] && e_poi[1] > poi[1]) {\n    return false;\n  }\n  if (e_poi[1] == poi[1] && s_poi[1] > poi[1]) {\n    return false;\n  }\n  if (s_poi[0] < poi[0] && e_poi[1] < poi[1]) {\n    return false;\n  }\n  let xseg = e_poi[0] - (e_poi[0] - s_poi[0]) * (e_poi[1] - poi[1]) / (e_poi[1] - s_poi[1]);\n  if (xseg < poi[0]) {\n    return false;\n  } else {\n    return true;\n  }\n}\n\nfunction isPoiWithinPoly(poi, poly, mercator) {\n  let sinsc = 0;\n  for (let i = 0; i < poly.length; i++) {\n    let epoly = poly[i][0];\n    if (poly.length == 1) {\n      epoly = poly[i][0]\n    }\n    for (let j = 0; j < epoly.length - 1; j++) {\n      let s_poi = epoly[j];\n      let e_poi = epoly[j + 1];\n      if (mercator) {\n        s_poi = lonlat2mercator(epoly[j][0], epoly[j][1]);\n        e_poi = lonlat2mercator(epoly[j + 1][0], epoly[j + 1][1]);\n      }\n      if (isRayIntersectsSegment(poi, s_poi, e_poi)) {\n        sinsc += 1;\n      }\n    }\n  }\n  if (sinsc % 2 == 1) {\n    return true;\n  } else {\n    return false;\n  }\n}\n\nfunction drawMapDataPoints(series, opts, config, context) {\n  var mapOption = assign({}, {\n    border: true,\n    mercator: false,\n    borderWidth: 1,\n    borderColor: '#666666',\n    fillOpacity: 0.6,\n    activeBorderColor: '#f04864',\n    activeFillColor: '#facc14',\n    activeFillOpacity: 1\n  }, opts.extra.map);\n  var coords, point;\n  var data = series;\n  var bounds = getBoundingBox(data);\n  if (mapOption.mercator) {\n    var max = lonlat2mercator(bounds.xMax, bounds.yMax)\n    var min = lonlat2mercator(bounds.xMin, bounds.yMin)\n    bounds.xMax = max[0]\n    bounds.yMax = max[1]\n    bounds.xMin = min[0]\n    bounds.yMin = min[1]\n  }\n  var xScale = opts.width / Math.abs(bounds.xMax - bounds.xMin);\n  var yScale = opts.height / Math.abs(bounds.yMax - bounds.yMin);\n  var scale = xScale < yScale ? xScale : yScale;\n  var xoffset = opts.width / 2 - Math.abs(bounds.xMax - bounds.xMin) / 2 * scale;\n  var yoffset = opts.height / 2 - Math.abs(bounds.yMax - bounds.yMin) / 2 * scale;\n  for (var i = 0; i < data.length; i++) {\n    context.beginPath();\n    context.setLineWidth(mapOption.borderWidth * opts.pix);\n    context.setStrokeStyle(mapOption.borderColor);\n    context.setFillStyle(hexToRgb(series[i].color, mapOption.fillOpacity));\n    if (opts.tooltip) {\n      if (opts.tooltip.index == i) {\n        context.setStrokeStyle(mapOption.activeBorderColor);\n        context.setFillStyle(hexToRgb(mapOption.activeFillColor, mapOption.activeFillOpacity));\n      }\n    }\n    var coorda = data[i].geometry.coordinates\n    for (var k = 0; k < coorda.length; k++) {\n      coords = coorda[k];\n      if (coords.length == 1) {\n        coords = coords[0]\n      }\n      for (var j = 0; j < coords.length; j++) {\n        var gaosi = Array(2);\n        if (mapOption.mercator) {\n          gaosi = lonlat2mercator(coords[j][0], coords[j][1])\n        } else {\n          gaosi = coords[j]\n        }\n        point = coordinateToPoint(gaosi[1], gaosi[0], bounds, scale, xoffset, yoffset)\n        if (j === 0) {\n          context.beginPath();\n          context.moveTo(point.x, point.y);\n        } else {\n          context.lineTo(point.x, point.y);\n        }\n      }\n      context.fill();\n      if (mapOption.border == true) {\n        context.stroke();\n      }\n    }\n  }\n  if (opts.dataLabel == true) {\n    for (var i = 0; i < data.length; i++) {\n      var centerPoint = data[i].properties.centroid;\n      if (centerPoint) {\n        if (mapOption.mercator) {\n          centerPoint = lonlat2mercator(data[i].properties.centroid[0], data[i].properties.centroid[1])\n        }\n        point = coordinateToPoint(centerPoint[1], centerPoint[0], bounds, scale, xoffset, yoffset);\n        let fontSize = data[i].textSize * opts.pix || config.fontSize;\n        let text = data[i].properties.name;\n        context.beginPath();\n        context.setFontSize(fontSize)\n        context.setFillStyle(data[i].textColor || opts.fontColor)\n        context.fillText(text, point.x - measureText(text, fontSize, context) / 2, point.y + fontSize / 2);\n        context.closePath();\n        context.stroke();\n      }\n    }\n  }\n  opts.chartData.mapData = {\n    bounds: bounds,\n    scale: scale,\n    xoffset: xoffset,\n    yoffset: yoffset,\n    mercator: mapOption.mercator\n  }\n  drawToolTipBridge(opts, config, context, 1);\n  context.draw();\n}\n\nfunction normalInt(min, max, iter) {\n  iter = iter == 0 ? 1 : iter;\n  var arr = [];\n  for (var i = 0; i < iter; i++) {\n    arr[i] = Math.random();\n  };\n  return Math.floor(arr.reduce(function(i, j) {\n    return i + j\n  }) / iter * (max - min)) + min;\n};\n\nfunction collisionNew(area, points, width, height) {\n  var isIn = false;\n  for (let i = 0; i < points.length; i++) {\n    if (points[i].area) {\n      if (area[3] < points[i].area[1] || area[0] > points[i].area[2] || area[1] > points[i].area[3] || area[2] < points[i].area[0]) {\n        if (area[0] < 0 || area[1] < 0 || area[2] > width || area[3] > height) {\n          isIn = true;\n          break;\n        } else {\n          isIn = false;\n        }\n      } else {\n        isIn = true;\n        break;\n      }\n    }\n  }\n  return isIn;\n};\n\nfunction getWordCloudPoint(opts, type, context) {\n  let points = opts.series;\n  switch (type) {\n    case 'normal':\n      for (let i = 0; i < points.length; i++) {\n        let text = points[i].name;\n        let tHeight = points[i].textSize * opts.pix;\n        let tWidth = measureText(text, tHeight, context);\n        let x, y;\n        let area;\n        let breaknum = 0;\n        while (true) {\n          breaknum++;\n          x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;\n          y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;\n          area = [x - 5 + opts.width / 2, y - 5 - tHeight + opts.height / 2, x + tWidth + 5 + opts.width / 2, y + 5 +\n            opts.height / 2\n          ];\n          let isCollision = collisionNew(area, points, opts.width, opts.height);\n          if (!isCollision) break;\n          if (breaknum == 1000) {\n            area = [-100, -100, -100, -100];\n            break;\n          }\n        };\n        points[i].area = area;\n      }\n      break;\n    case 'vertical':\n      function Spin() {\n        //获取均匀随机值，是否旋转，旋转的概率为（1-0.5）\n        if (Math.random() > 0.7) {\n          return true;\n        } else {\n          return false\n        };\n      };\n      for (let i = 0; i < points.length; i++) {\n        let text = points[i].name;\n        let tHeight = points[i].textSize * opts.pix;\n        let tWidth = measureText(text, tHeight, context);\n        let isSpin = Spin();\n        let x, y, area, areav;\n        let breaknum = 0;\n        while (true) {\n          breaknum++;\n          let isCollision;\n          if (isSpin) {\n            x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;\n            y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;\n            area = [y - 5 - tWidth + opts.width / 2, (-x - 5 + opts.height / 2), y + 5 + opts.width / 2, (-x + tHeight + 5 + opts.height / 2)];\n            areav = [opts.width - (opts.width / 2 - opts.height / 2) - (-x + tHeight + 5 + opts.height / 2) - 5, (opts.height / 2 - opts.width / 2) + (y - 5 - tWidth + opts.width / 2) - 5, opts.width - (opts.width / 2 - opts.height / 2) - (-x + tHeight + 5 + opts.height / 2) + tHeight, (opts.height / 2 - opts.width / 2) + (y - 5 - tWidth + opts.width / 2) + tWidth + 5];\n            isCollision = collisionNew(areav, points, opts.height, opts.width);\n          } else {\n            x = normalInt(-opts.width / 2, opts.width / 2, 5) - tWidth / 2;\n            y = normalInt(-opts.height / 2, opts.height / 2, 5) + tHeight / 2;\n            area = [x - 5 + opts.width / 2, y - 5 - tHeight + opts.height / 2, x + tWidth + 5 + opts.width / 2, y + 5 + opts.height / 2];\n            isCollision = collisionNew(area, points, opts.width, opts.height);\n          }\n          if (!isCollision) break;\n          if (breaknum == 1000) {\n            area = [-1000, -1000, -1000, -1000];\n            break;\n          }\n        };\n        if (isSpin) {\n          points[i].area = areav;\n          points[i].areav = area;\n        } else {\n          points[i].area = area;\n        }\n        points[i].rotate = isSpin;\n      };\n      break;\n  }\n  return points;\n}\n\nfunction drawWordCloudDataPoints(series, opts, config, context) {\n  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  let wordOption = assign({}, {\n    type: 'normal',\n    autoColors: true\n  }, opts.extra.word);\n  if (!opts.chartData.wordCloudData) {\n    opts.chartData.wordCloudData = getWordCloudPoint(opts, wordOption.type, context);\n  }\n  context.beginPath();\n  context.setFillStyle(opts.background);\n  context.rect(0, 0, opts.width, opts.height);\n  context.fill();\n  context.save();\n  let points = opts.chartData.wordCloudData;\n  context.translate(opts.width / 2, opts.height / 2);\n  for (let i = 0; i < points.length; i++) {\n    context.save();\n    if (points[i].rotate) {\n      context.rotate(90 * Math.PI / 180);\n    }\n    let text = points[i].name;\n    let tHeight = points[i].textSize * opts.pix;\n    let tWidth = measureText(text, tHeight, context);\n    context.beginPath();\n    context.setStrokeStyle(points[i].color);\n    context.setFillStyle(points[i].color);\n    context.setFontSize(tHeight);\n    if (points[i].rotate) {\n      if (points[i].areav[0] > 0) {\n        if (opts.tooltip) {\n          if (opts.tooltip.index == i) {\n            context.strokeText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);\n          } else {\n            context.fillText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);\n          }\n        } else {\n          context.fillText(text, (points[i].areav[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].areav[1] + 5 + tHeight - opts.height / 2) * process);\n        }\n      }\n    } else {\n      if (points[i].area[0] > 0) {\n        if (opts.tooltip) {\n          if (opts.tooltip.index == i) {\n            context.strokeText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);\n          } else {\n            context.fillText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);\n          }\n        } else {\n          context.fillText(text, (points[i].area[0] + 5 - opts.width / 2) * process - tWidth * (1 - process) / 2, (points[i].area[1] + 5 + tHeight - opts.height / 2) * process);\n        }\n      }\n    }\n    context.stroke();\n    context.restore();\n  }\n  context.restore();\n}\n\nfunction drawFunnelDataPoints(series, opts, config, context) {\n  let process = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;\n  let funnelOption = assign({}, {\n    type:'funnel',\n    activeWidth: 10,\n    activeOpacity: 0.3,\n    border: false,\n    borderWidth: 2,\n    borderColor: '#FFFFFF',\n    fillOpacity: 1,\n    labelAlign: 'right',\n    linearType: 'none',\n    customColor: [],\n  }, opts.extra.funnel);\n  let eachSpacing = (opts.height - opts.area[0] - opts.area[2]) / series.length;\n  let centerPosition = {\n    x: opts.area[3] + (opts.width - opts.area[1] - opts.area[3]) / 2,\n    y: opts.height - opts.area[2]\n  };\n  let activeWidth = funnelOption.activeWidth * opts.pix;\n  let radius = Math.min((opts.width - opts.area[1] - opts.area[3]) / 2 - activeWidth, (opts.height - opts.area[0] - opts.area[2]) / 2 - activeWidth);\n  series = getFunnelDataPoints(series, radius, funnelOption.type, eachSpacing, process);\n  context.save();\n  context.translate(centerPosition.x, centerPosition.y);\n  funnelOption.customColor = fillCustomColor(funnelOption.linearType, funnelOption.customColor, series, config);\n  if(funnelOption.type == 'pyramid'){\n    for (let i = 0; i < series.length; i++) {\n      if (i == series.length -1) {\n        if (opts.tooltip) {\n          if (opts.tooltip.index == i) {\n            context.beginPath();\n            context.setFillStyle(hexToRgb(series[i].color, funnelOption.activeOpacity));\n            context.moveTo(-activeWidth, -eachSpacing);\n            context.lineTo(-series[i].radius - activeWidth, 0);\n            context.lineTo(series[i].radius + activeWidth, 0);\n            context.lineTo(activeWidth, -eachSpacing);\n            context.lineTo(-activeWidth, -eachSpacing);\n            context.closePath();\n            context.fill();\n          }\n        }\n        series[i].funnelArea = [centerPosition.x - series[i].radius, centerPosition.y - eachSpacing * (i + 1), centerPosition.x + series[i].radius, centerPosition.y - eachSpacing * i];\n        context.beginPath();\n        context.setLineWidth(funnelOption.borderWidth * opts.pix);\n        context.setStrokeStyle(funnelOption.borderColor);\n        var fillColor = hexToRgb(series[i].color, funnelOption.fillOpacity);\n        if (funnelOption.linearType == 'custom') {\n          var grd = context.createLinearGradient(series[i].radius, -eachSpacing, -series[i].radius, -eachSpacing);\n          grd.addColorStop(0, hexToRgb(series[i].color, funnelOption.fillOpacity));\n          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[series[i].linearIndex], funnelOption.fillOpacity));\n          grd.addColorStop(1, hexToRgb(series[i].color, funnelOption.fillOpacity));\n          fillColor = grd\n        }\n        context.setFillStyle(fillColor);\n        context.moveTo(0, -eachSpacing);\n        context.lineTo(-series[i].radius, 0);\n        context.lineTo(series[i].radius, 0);\n        context.lineTo(0, -eachSpacing);\n        context.closePath();\n        context.fill();\n        if (funnelOption.border == true) {\n          context.stroke();\n        }\n      } else {\n        if (opts.tooltip) {\n          if (opts.tooltip.index == i) {\n            context.beginPath();\n            context.setFillStyle(hexToRgb(series[i].color, funnelOption.activeOpacity));\n            context.moveTo(0, 0);\n            context.lineTo(-series[i].radius - activeWidth, 0);\n            context.lineTo(-series[i + 1].radius - activeWidth, -eachSpacing);\n            context.lineTo(series[i + 1].radius + activeWidth, -eachSpacing);\n            context.lineTo(series[i].radius + activeWidth, 0);\n            context.lineTo(0, 0);\n            context.closePath();\n            context.fill();\n          }\n        }\n        series[i].funnelArea = [centerPosition.x - series[i].radius, centerPosition.y - eachSpacing * (i + 1), centerPosition.x + series[i].radius, centerPosition.y - eachSpacing * i];\n        context.beginPath();\n        context.setLineWidth(funnelOption.borderWidth * opts.pix);\n        context.setStrokeStyle(funnelOption.borderColor);\n        var fillColor = hexToRgb(series[i].color, funnelOption.fillOpacity);\n        if (funnelOption.linearType == 'custom') {\n          var grd = context.createLinearGradient(series[i].radius, -eachSpacing, -series[i].radius, -eachSpacing);\n          grd.addColorStop(0, hexToRgb(series[i].color, funnelOption.fillOpacity));\n          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[series[i].linearIndex], funnelOption.fillOpacity));\n          grd.addColorStop(1, hexToRgb(series[i].color, funnelOption.fillOpacity));\n          fillColor = grd\n        }\n        context.setFillStyle(fillColor);\n        context.moveTo(0, 0);\n        context.lineTo(-series[i].radius, 0);\n        context.lineTo(-series[i + 1].radius, -eachSpacing);\n        context.lineTo(series[i + 1].radius, -eachSpacing);\n        context.lineTo(series[i].radius, 0);\n        context.lineTo(0, 0);\n        context.closePath();\n        context.fill();\n        if (funnelOption.border == true) {\n          context.stroke();\n        }\n      }\n      context.translate(0, -eachSpacing)\n    }\n  }else{\n    for (let i = 0; i < series.length; i++) {\n      if (i == 0) {\n        if (opts.tooltip) {\n          if (opts.tooltip.index == i) {\n            context.beginPath();\n            context.setFillStyle(hexToRgb(series[i].color, funnelOption.activeOpacity));\n            context.moveTo(-activeWidth, 0);\n            context.lineTo(-series[i].radius - activeWidth, -eachSpacing);\n            context.lineTo(series[i].radius + activeWidth, -eachSpacing);\n            context.lineTo(activeWidth, 0);\n            context.lineTo(-activeWidth, 0);\n            context.closePath();\n            context.fill();\n          }\n        }\n        series[i].funnelArea = [centerPosition.x - series[i].radius, centerPosition.y - eachSpacing, centerPosition.x + series[i].radius, centerPosition.y];\n        context.beginPath();\n        context.setLineWidth(funnelOption.borderWidth * opts.pix);\n        context.setStrokeStyle(funnelOption.borderColor);\n        var fillColor = hexToRgb(series[i].color, funnelOption.fillOpacity);\n        if (funnelOption.linearType == 'custom') {\n          var grd = context.createLinearGradient(series[i].radius, -eachSpacing, -series[i].radius, -eachSpacing);\n          grd.addColorStop(0, hexToRgb(series[i].color, funnelOption.fillOpacity));\n          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[series[i].linearIndex], funnelOption.fillOpacity));\n          grd.addColorStop(1, hexToRgb(series[i].color, funnelOption.fillOpacity));\n          fillColor = grd\n        }\n        context.setFillStyle(fillColor);\n        context.moveTo(0, 0);\n        context.lineTo(-series[i].radius, -eachSpacing);\n        context.lineTo(series[i].radius, -eachSpacing);\n        context.lineTo(0, 0);\n        context.closePath();\n        context.fill();\n        if (funnelOption.border == true) {\n          context.stroke();\n        }\n      } else {\n        if (opts.tooltip) {\n          if (opts.tooltip.index == i) {\n            context.beginPath();\n            context.setFillStyle(hexToRgb(series[i].color, funnelOption.activeOpacity));\n            context.moveTo(0, 0);\n            context.lineTo(-series[i - 1].radius - activeWidth, 0);\n            context.lineTo(-series[i].radius - activeWidth, -eachSpacing);\n            context.lineTo(series[i].radius + activeWidth, -eachSpacing);\n            context.lineTo(series[i - 1].radius + activeWidth, 0);\n            context.lineTo(0, 0);\n            context.closePath();\n            context.fill();\n          }\n        }\n        series[i].funnelArea = [centerPosition.x - series[i].radius, centerPosition.y - eachSpacing * (i + 1), centerPosition.x + series[i].radius, centerPosition.y - eachSpacing * i];\n        context.beginPath();\n        context.setLineWidth(funnelOption.borderWidth * opts.pix);\n        context.setStrokeStyle(funnelOption.borderColor);\n        var fillColor = hexToRgb(series[i].color, funnelOption.fillOpacity);\n        if (funnelOption.linearType == 'custom') {\n          var grd = context.createLinearGradient(series[i].radius, -eachSpacing, -series[i].radius, -eachSpacing);\n          grd.addColorStop(0, hexToRgb(series[i].color, funnelOption.fillOpacity));\n          grd.addColorStop(0.5, hexToRgb(funnelOption.customColor[series[i].linearIndex], funnelOption.fillOpacity));\n          grd.addColorStop(1, hexToRgb(series[i].color, funnelOption.fillOpacity));\n          fillColor = grd\n        }\n        context.setFillStyle(fillColor);\n        context.moveTo(0, 0);\n        context.lineTo(-series[i - 1].radius, 0);\n        context.lineTo(-series[i].radius, -eachSpacing);\n        context.lineTo(series[i].radius, -eachSpacing);\n        context.lineTo(series[i - 1].radius, 0);\n        context.lineTo(0, 0);\n        context.closePath();\n        context.fill();\n        if (funnelOption.border == true) {\n          context.stroke();\n        }\n      }\n      context.translate(0, -eachSpacing)\n    }\n  }\n  \n  context.restore();\n  if (opts.dataLabel !== false && process === 1) {\n    drawFunnelText(series, opts, context, eachSpacing, funnelOption.labelAlign, activeWidth, centerPosition);\n  }\n  return {\n    center: centerPosition,\n    radius: radius,\n    series: series\n  };\n}\n\nfunction drawFunnelText(series, opts, context, eachSpacing, labelAlign, activeWidth, centerPosition) {\n  for (let i = 0; i < series.length; i++) {\n    let item = series[i];\n    if(item.labelShow === false){\n      continue;\n    }\n    let startX, endX, startY, fontSize;\n    let text = item.formatter ? item.formatter(item,i,series,opts) : util.toFixed(item._proportion_ * 100) + '%';\n    text = item.labelText ? item.labelText : text;\n    if (labelAlign == 'right') {\n      if(opts.extra.funnel.type === 'pyramid'){\n        if (i == series.length -1) {\n          startX = (item.funnelArea[2] + centerPosition.x) / 2;\n        } else {\n          startX = (item.funnelArea[2] + series[i + 1].funnelArea[2]) / 2;\n        }\n      }else{\n        if (i == 0) {\n          startX = (item.funnelArea[2] + centerPosition.x) / 2;\n        } else {\n          startX = (item.funnelArea[2] + series[i - 1].funnelArea[2]) / 2;\n        }\n      }\n      endX = startX + activeWidth * 2;\n      startY = item.funnelArea[1] + eachSpacing / 2;\n      fontSize = item.textSize * opts.pix || opts.fontSize * opts.pix;\n      context.setLineWidth(1 * opts.pix);\n      context.setStrokeStyle(item.color);\n      context.setFillStyle(item.color);\n      context.beginPath();\n      context.moveTo(startX, startY);\n      context.lineTo(endX, startY);\n      context.stroke();\n      context.closePath();\n      context.beginPath();\n      context.moveTo(endX, startY);\n      context.arc(endX, startY, 2 * opts.pix, 0, 2 * Math.PI);\n      context.closePath();\n      context.fill();\n      context.beginPath();\n      context.setFontSize(fontSize);\n      context.setFillStyle(item.textColor || opts.fontColor);\n      context.fillText(text, endX + 5, startY + fontSize / 2 - 2);\n      context.closePath();\n      context.stroke();\n      context.closePath();\n    } else {\n      if(opts.extra.funnel.type === 'pyramid'){\n        if (i == series.length -1) {\n          startX = (item.funnelArea[0] + centerPosition.x) / 2;\n        } else {\n          startX = (item.funnelArea[0] + series[i + 1].funnelArea[0]) / 2;\n        }\n      }else{\n        if (i == 0) {\n          startX = (item.funnelArea[0] + centerPosition.x) / 2;\n        } else {\n          startX = (item.funnelArea[0] + series[i - 1].funnelArea[0]) / 2;\n        }\n      }\n      endX = startX - activeWidth * 2;\n      startY = item.funnelArea[1] + eachSpacing / 2;\n      fontSize = item.textSize * opts.pix || opts.fontSize * opts.pix;\n      context.setLineWidth(1 * opts.pix);\n      context.setStrokeStyle(item.color);\n      context.setFillStyle(item.color);\n      context.beginPath();\n      context.moveTo(startX, startY);\n      context.lineTo(endX, startY);\n      context.stroke();\n      context.closePath();\n      context.beginPath();\n      context.moveTo(endX, startY);\n      context.arc(endX, startY, 2, 0, 2 * Math.PI);\n      context.closePath();\n      context.fill();\n      context.beginPath();\n      context.setFontSize(fontSize);\n      context.setFillStyle(item.textColor || opts.fontColor);\n      context.fillText(text, endX - 5 - measureText(text, fontSize, context), startY + fontSize / 2 - 2);\n      context.closePath();\n      context.stroke();\n      context.closePath();\n    }\n\n  }\n}\n\n\nfunction drawCanvas(opts, context) {\n  context.draw();\n}\n\nvar Timing = {\n  easeIn: function easeIn(pos) {\n    return Math.pow(pos, 3);\n  },\n  easeOut: function easeOut(pos) {\n    return Math.pow(pos - 1, 3) + 1;\n  },\n  easeInOut: function easeInOut(pos) {\n    if ((pos /= 0.5) < 1) {\n      return 0.5 * Math.pow(pos, 3);\n    } else {\n      return 0.5 * (Math.pow(pos - 2, 3) + 2);\n    }\n  },\n  linear: function linear(pos) {\n    return pos;\n  }\n};\n\nfunction Animation(opts) {\n  this.isStop = false;\n  opts.duration = typeof opts.duration === 'undefined' ? 1000 : opts.duration;\n  opts.timing = opts.timing || 'easeInOut';\n  var delay = 17;\n  function createAnimationFrame() {\n    if (typeof setTimeout !== 'undefined') {\n      return function(step, delay) {\n        setTimeout(function() {\n          var timeStamp = +new Date();\n          step(timeStamp);\n        }, delay);\n      };\n    } else if (typeof requestAnimationFrame !== 'undefined') {\n      return requestAnimationFrame;\n    } else {\n      return function(step) {\n        step(null);\n      };\n    }\n  };\n  var animationFrame = createAnimationFrame();\n  var startTimeStamp = null;\n  var _step = function step(timestamp) {\n    if (timestamp === null || this.isStop === true) {\n      opts.onProcess && opts.onProcess(1);\n      opts.onAnimationFinish && opts.onAnimationFinish();\n      return;\n    }\n    if (startTimeStamp === null) {\n      startTimeStamp = timestamp;\n    }\n    if (timestamp - startTimeStamp < opts.duration) {\n      var process = (timestamp - startTimeStamp) / opts.duration;\n      var timingFunction = Timing[opts.timing];\n      process = timingFunction(process);\n      opts.onProcess && opts.onProcess(process);\n      animationFrame(_step, delay);\n    } else {\n      opts.onProcess && opts.onProcess(1);\n      opts.onAnimationFinish && opts.onAnimationFinish();\n    }\n  };\n  _step = _step.bind(this);\n  animationFrame(_step, delay);\n}\n\nAnimation.prototype.stop = function() {\n  this.isStop = true;\n};\n\nfunction drawCharts(type, opts, config, context) {\n  var _this = this;\n  var series = opts.series;\n  //兼容ECharts饼图类数据格式\n  if (type === 'pie' || type === 'ring' || type === 'mount' || type === 'rose' || type === 'funnel') {\n    series = fixPieSeries(series, opts, config);\n  }\n  var categories = opts.categories;\n  if (type === 'mount') {\n    categories = [];\n    for (let j = 0; j < series.length; j++) {\n      if(series[j].show !== false) categories.push(series[j].name)\n    }\n    opts.categories = categories;\n  }\n  series = fillSeries(series, opts, config);\n  var duration = opts.animation ? opts.duration : 0;\n  _this.animationInstance && _this.animationInstance.stop();\n  var seriesMA = null;\n  if (type == 'candle') {\n    let average = assign({}, opts.extra.candle.average);\n    if (average.show) {\n      seriesMA = calCandleMA(average.day, average.name, average.color, series[0].data);\n      seriesMA = fillSeries(seriesMA, opts, config);\n      opts.seriesMA = seriesMA;\n    } else if (opts.seriesMA) {\n      seriesMA = opts.seriesMA = fillSeries(opts.seriesMA, opts, config);\n    } else {\n      seriesMA = series;\n    }\n  } else {\n    seriesMA = series;\n  }\n  /* 过滤掉show=false的series */\n  opts._series_ = series = filterSeries(series);\n  //重新计算图表区域\n  opts.area = new Array(4);\n  //复位绘图区域\n  for (let j = 0; j < 4; j++) {\n    opts.area[j] = opts.padding[j] * opts.pix;\n  }\n  //通过计算三大区域：图例、X轴、Y轴的大小，确定绘图区域\n  var _calLegendData = calLegendData(seriesMA, opts, config, opts.chartData, context),\n    legendHeight = _calLegendData.area.wholeHeight,\n    legendWidth = _calLegendData.area.wholeWidth;\n\n  switch (opts.legend.position) {\n    case 'top':\n      opts.area[0] += legendHeight;\n      break;\n    case 'bottom':\n      opts.area[2] += legendHeight;\n      break;\n    case 'left':\n      opts.area[3] += legendWidth;\n      break;\n    case 'right':\n      opts.area[1] += legendWidth;\n      break;\n  }\n\n  let _calYAxisData = {},\n    yAxisWidth = 0;\n  if (opts.type === 'line' || opts.type === 'column'|| opts.type === 'mount' || opts.type === 'area' || opts.type === 'mix' || opts.type === 'candle' || opts.type === 'scatter'  || opts.type === 'bubble' || opts.type === 'bar') {\n      _calYAxisData = calYAxisData(series, opts, config, context);\n      yAxisWidth = _calYAxisData.yAxisWidth;\n    //如果显示Y轴标题\n    if (opts.yAxis.showTitle) {\n      let maxTitleHeight = 0;\n      for (let i = 0; i < opts.yAxis.data.length; i++) {\n        maxTitleHeight = Math.max(maxTitleHeight, opts.yAxis.data[i].titleFontSize ? opts.yAxis.data[i].titleFontSize * opts.pix : config.fontSize)\n      }\n      opts.area[0] += maxTitleHeight;\n    }\n    let rightIndex = 0,\n      leftIndex = 0;\n    //计算主绘图区域左右位置\n    for (let i = 0; i < yAxisWidth.length; i++) {\n      if (yAxisWidth[i].position == 'left') {\n        if (leftIndex > 0) {\n          opts.area[3] += yAxisWidth[i].width + opts.yAxis.padding * opts.pix;\n        } else {\n          opts.area[3] += yAxisWidth[i].width;\n        }\n        leftIndex += 1;\n      } else if (yAxisWidth[i].position == 'right') {\n        if (rightIndex > 0) {\n          opts.area[1] += yAxisWidth[i].width + opts.yAxis.padding * opts.pix;\n        } else {\n          opts.area[1] += yAxisWidth[i].width;\n        }\n        rightIndex += 1;\n      }\n    }\n  } else {\n    config.yAxisWidth = yAxisWidth;\n  }\n  opts.chartData.yAxisData = _calYAxisData;\n\n  if (opts.categories && opts.categories.length && opts.type !== 'radar' && opts.type !== 'gauge' && opts.type !== 'bar') {\n    opts.chartData.xAxisData = getXAxisPoints(opts.categories, opts, config);\n    let _calCategoriesData = calCategoriesData(opts.categories, opts, config, opts.chartData.xAxisData.eachSpacing, context),\n      xAxisHeight = _calCategoriesData.xAxisHeight,\n      angle = _calCategoriesData.angle;\n    config.xAxisHeight = xAxisHeight;\n    config._xAxisTextAngle_ = angle;\n    opts.area[2] += xAxisHeight;\n    opts.chartData.categoriesData = _calCategoriesData;\n  } else {\n    if (opts.type === 'line' || opts.type === 'area' || opts.type === 'scatter' || opts.type === 'bubble' || opts.type === 'bar') {\n      opts.chartData.xAxisData = calXAxisData(series, opts, config, context);\n      categories = opts.chartData.xAxisData.rangesFormat;\n      let _calCategoriesData = calCategoriesData(categories, opts, config, opts.chartData.xAxisData.eachSpacing, context),\n        xAxisHeight = _calCategoriesData.xAxisHeight,\n        angle = _calCategoriesData.angle;\n      config.xAxisHeight = xAxisHeight;\n      config._xAxisTextAngle_ = angle;\n      opts.area[2] += xAxisHeight;\n      opts.chartData.categoriesData = _calCategoriesData;\n    } else {\n      opts.chartData.xAxisData = {\n        xAxisPoints: []\n      };\n    }\n  }\n\n  //计算右对齐偏移距离\n  if (opts.enableScroll && opts.xAxis.scrollAlign == 'right' && opts._scrollDistance_ === undefined) {\n    let offsetLeft = 0,\n      xAxisPoints = opts.chartData.xAxisData.xAxisPoints,\n      startX = opts.chartData.xAxisData.startX,\n      endX = opts.chartData.xAxisData.endX,\n      eachSpacing = opts.chartData.xAxisData.eachSpacing;\n    let totalWidth = eachSpacing * (xAxisPoints.length - 1);\n    let screenWidth = endX - startX;\n    offsetLeft = screenWidth - totalWidth;\n    _this.scrollOption.currentOffset = offsetLeft;\n    _this.scrollOption.startTouchX = offsetLeft;\n    _this.scrollOption.distance = 0;\n    _this.scrollOption.lastMoveTime = 0;\n    opts._scrollDistance_ = offsetLeft;\n  }\n\n  if (type === 'pie' || type === 'ring' || type === 'rose') {\n    config._pieTextMaxLength_ = opts.dataLabel === false ? 0 : getPieTextMaxLength(seriesMA, config, context, opts);\n  }\n  \n  switch (type) {\n    case 'word':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawWordCloudDataPoints(series, opts, config, context, process);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'map':\n      context.clearRect(0, 0, opts.width, opts.height);\n      drawMapDataPoints(series, opts, config, context);\n      break;\n    case 'funnel':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          opts.chartData.funnelData = drawFunnelDataPoints(series, opts, config, context, process);\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'line':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawYAxisGrid(categories, opts, config, context);\n          drawXAxis(categories, opts, config, context);\n          var _drawLineDataPoints = drawLineDataPoints(series, opts, config, context, process),\n            xAxisPoints = _drawLineDataPoints.xAxisPoints,\n            calPoints = _drawLineDataPoints.calPoints,\n            eachSpacing = _drawLineDataPoints.eachSpacing;\n          opts.chartData.xAxisPoints = xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'scatter':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawYAxisGrid(categories, opts, config, context);\n          drawXAxis(categories, opts, config, context);\n          var _drawScatterDataPoints = drawScatterDataPoints(series, opts, config, context, process),\n            xAxisPoints = _drawScatterDataPoints.xAxisPoints,\n            calPoints = _drawScatterDataPoints.calPoints,\n            eachSpacing = _drawScatterDataPoints.eachSpacing;\n          opts.chartData.xAxisPoints = xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'bubble':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawYAxisGrid(categories, opts, config, context);\n          drawXAxis(categories, opts, config, context);\n          var _drawBubbleDataPoints = drawBubbleDataPoints(series, opts, config, context, process),\n            xAxisPoints = _drawBubbleDataPoints.xAxisPoints,\n            calPoints = _drawBubbleDataPoints.calPoints,\n            eachSpacing = _drawBubbleDataPoints.eachSpacing;\n          opts.chartData.xAxisPoints = xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'mix':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawYAxisGrid(categories, opts, config, context);\n          drawXAxis(categories, opts, config, context);\n          var _drawMixDataPoints = drawMixDataPoints(series, opts, config, context, process),\n            xAxisPoints = _drawMixDataPoints.xAxisPoints,\n            calPoints = _drawMixDataPoints.calPoints,\n            eachSpacing = _drawMixDataPoints.eachSpacing;\n          opts.chartData.xAxisPoints = xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'column':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawYAxisGrid(categories, opts, config, context);\n          drawXAxis(categories, opts, config, context);\n          var _drawColumnDataPoints = drawColumnDataPoints(series, opts, config, context, process),\n            xAxisPoints = _drawColumnDataPoints.xAxisPoints,\n            calPoints = _drawColumnDataPoints.calPoints,\n            eachSpacing = _drawColumnDataPoints.eachSpacing;\n          opts.chartData.xAxisPoints = xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'mount':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawYAxisGrid(categories, opts, config, context);\n          drawXAxis(categories, opts, config, context);\n          var _drawMountDataPoints = drawMountDataPoints(series, opts, config, context, process),\n            xAxisPoints = _drawMountDataPoints.xAxisPoints,\n            calPoints = _drawMountDataPoints.calPoints,\n            eachSpacing = _drawMountDataPoints.eachSpacing;\n          opts.chartData.xAxisPoints = xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'bar':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawXAxis(categories, opts, config, context);\n          var _drawBarDataPoints = drawBarDataPoints(series, opts, config, context, process),\n            yAxisPoints = _drawBarDataPoints.yAxisPoints,\n            calPoints = _drawBarDataPoints.calPoints,\n            eachSpacing = _drawBarDataPoints.eachSpacing;\n          opts.chartData.yAxisPoints = yAxisPoints;\n          opts.chartData.xAxisPoints = opts.chartData.xAxisData.xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process, eachSpacing, yAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'area':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawYAxisGrid(categories, opts, config, context);\n          drawXAxis(categories, opts, config, context);\n          var _drawAreaDataPoints = drawAreaDataPoints(series, opts, config, context, process),\n            xAxisPoints = _drawAreaDataPoints.xAxisPoints,\n            calPoints = _drawAreaDataPoints.calPoints,\n            eachSpacing = _drawAreaDataPoints.eachSpacing;\n          opts.chartData.xAxisPoints = xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'ring':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          opts.chartData.pieData = drawPieDataPoints(series, opts, config, context, process);\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'pie':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          opts.chartData.pieData = drawPieDataPoints(series, opts, config, context, process);\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'rose':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          opts.chartData.pieData = drawRoseDataPoints(series, opts, config, context, process);\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'radar':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          opts.chartData.radarData = drawRadarDataPoints(series, opts, config, context, process);\n          drawLegend(opts.series, opts, config, context, opts.chartData);\n          drawToolTipBridge(opts, config, context, process);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'arcbar':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          opts.chartData.arcbarData = drawArcbarDataPoints(series, opts, config, context, process);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'gauge':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          opts.chartData.gaugeData = drawGaugeDataPoints(categories, series, opts, config, context, process);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n    case 'candle':\n      this.animationInstance = new Animation({\n        timing: opts.timing,\n        duration: duration,\n        onProcess: function onProcess(process) {\n          context.clearRect(0, 0, opts.width, opts.height);\n          if (opts.rotate) {\n            contextRotate(context, opts);\n          }\n          drawYAxisGrid(categories, opts, config, context);\n          drawXAxis(categories, opts, config, context);\n          var _drawCandleDataPoints = drawCandleDataPoints(series, seriesMA, opts, config, context, process),\n            xAxisPoints = _drawCandleDataPoints.xAxisPoints,\n            calPoints = _drawCandleDataPoints.calPoints,\n            eachSpacing = _drawCandleDataPoints.eachSpacing;\n          opts.chartData.xAxisPoints = xAxisPoints;\n          opts.chartData.calPoints = calPoints;\n          opts.chartData.eachSpacing = eachSpacing;\n          drawYAxis(series, opts, config, context);\n          if (opts.enableMarkLine !== false && process === 1) {\n            drawMarkLine(opts, config, context);\n          }\n          if (seriesMA) {\n            drawLegend(seriesMA, opts, config, context, opts.chartData);\n          } else {\n            drawLegend(opts.series, opts, config, context, opts.chartData);\n          }\n          drawToolTipBridge(opts, config, context, process, eachSpacing, xAxisPoints);\n          drawCanvas(opts, context);\n        },\n        onAnimationFinish: function onAnimationFinish() {\n          _this.uevent.trigger('renderComplete');\n        }\n      });\n      break;\n  }\n}\n\nfunction uChartsEvent() {\n  this.events = {};\n}\n\nuChartsEvent.prototype.addEventListener = function(type, listener) {\n  this.events[type] = this.events[type] || [];\n  this.events[type].push(listener);\n};\n\nuChartsEvent.prototype.delEventListener = function(type) {\n  this.events[type] = [];\n};\n\nuChartsEvent.prototype.trigger = function() {\n  for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {\n    args[_key] = arguments[_key];\n  }\n  var type = args[0];\n  var params = args.slice(1);\n  if (!!this.events[type]) {\n    this.events[type].forEach(function(listener) {\n      try {\n        listener.apply(null, params);\n      } catch (e) {\n          //console.log('[uCharts] '+e);\n      }\n    });\n  }\n};\n\nvar uCharts = function uCharts(opts) {\n  opts.pix = opts.pixelRatio ? opts.pixelRatio : 1;\n  opts.fontSize = opts.fontSize ? opts.fontSize : 13;\n  opts.fontColor = opts.fontColor ? opts.fontColor : config.fontColor;\n  if (opts.background == \"\" || opts.background == \"none\") {\n    opts.background = \"#FFFFFF\"\n  }\n  opts.title = assign({}, opts.title);\n  opts.subtitle = assign({}, opts.subtitle);\n  opts.duration = opts.duration ? opts.duration : 1000;\n  opts.yAxis = assign({}, {\n    data: [],\n    showTitle: false,\n    disabled: false,\n    disableGrid: false,\n    splitNumber: 5,\n    gridType: 'solid',\n    dashLength: 4 * opts.pix,\n    gridColor: '#cccccc',\n    padding: 10,\n    fontColor: '#666666'\n  }, opts.yAxis);\n  opts.xAxis = assign({}, {\n    rotateLabel: false,\n    rotateAngle:45,\n    disabled: false,\n    disableGrid: false,\n    splitNumber: 5,\n    calibration:false,\n    gridType: 'solid',\n    dashLength: 4,\n    scrollAlign: 'left',\n    boundaryGap: 'center',\n    axisLine: true,\n    axisLineColor: '#cccccc'\n  }, opts.xAxis);\n  opts.xAxis.scrollPosition = opts.xAxis.scrollAlign;\n  opts.legend = assign({}, {\n    show: true,\n    position: 'bottom',\n    float: 'center',\n    backgroundColor: 'rgba(0,0,0,0)',\n    borderColor: 'rgba(0,0,0,0)',\n    borderWidth: 0,\n    padding: 5,\n    margin: 5,\n    itemGap: 10,\n    fontSize: opts.fontSize,\n    lineHeight: opts.fontSize,\n    fontColor: opts.fontColor,\n    formatter: {},\n    hiddenColor: '#CECECE'\n  }, opts.legend);\n  opts.extra = assign({}, opts.extra);\n  opts.rotate = opts.rotate ? true : false;\n  opts.animation = opts.animation ? true : false;\n  opts.rotate = opts.rotate ? true : false;\n  opts.canvas2d = opts.canvas2d ? true : false;\n  \n  let config$$1 = assign({}, config);\n  config$$1.color = opts.color ? opts.color : config$$1.color;\n  if (opts.type == 'pie') {\n    config$$1.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.pie.labelWidth * opts.pix || config$$1.pieChartLinePadding * opts.pix;\n  }\n  if (opts.type == 'ring') {\n    config$$1.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.ring.labelWidth * opts.pix || config$$1.pieChartLinePadding * opts.pix;\n  }\n  if (opts.type == 'rose') {\n    config$$1.pieChartLinePadding = opts.dataLabel === false ? 0 : opts.extra.rose.labelWidth * opts.pix || config$$1.pieChartLinePadding * opts.pix;\n  }\n  config$$1.pieChartTextPadding = opts.dataLabel === false ? 0 : config$$1.pieChartTextPadding * opts.pix;\n\n  //屏幕旋转\n  config$$1.rotate = opts.rotate;\n  if (opts.rotate) {\n    let tempWidth = opts.width;\n    let tempHeight = opts.height;\n    opts.width = tempHeight;\n    opts.height = tempWidth;\n  }\n\n  //适配高分屏\n  opts.padding = opts.padding ? opts.padding : config$$1.padding;\n  config$$1.yAxisWidth = config.yAxisWidth * opts.pix;\n  config$$1.xAxisHeight = config.xAxisHeight * opts.pix;\n  if (opts.enableScroll && opts.xAxis.scrollShow) {\n    config$$1.xAxisHeight += 6 * opts.pix;\n  }\n  config$$1.fontSize = opts.fontSize * opts.pix;\n  config$$1.titleFontSize = config.titleFontSize * opts.pix;\n  config$$1.subtitleFontSize = config.subtitleFontSize * opts.pix;\n  config$$1.toolTipPadding = config.toolTipPadding * opts.pix;\n  config$$1.toolTipLineHeight = config.toolTipLineHeight * opts.pix;\n  if(!opts.context){\n    throw new Error('[uCharts] 未获取到context！注意：v2.0版本后，需要自行获取canvas的绘图上下文并传入opts.context！');\n  }\n  this.context = opts.context;\n  if (!this.context.setTextAlign) {\n    this.context.setStrokeStyle = function(e) {\n      return this.strokeStyle = e;\n    }\n    this.context.setLineWidth = function(e) {\n      return this.lineWidth = e;\n    }\n    this.context.setLineCap = function(e) {\n      return this.lineCap = e;\n    }\n    this.context.setFontSize = function(e) {\n      return this.font = e + \"px sans-serif\";\n    }\n    this.context.setFillStyle = function(e) {\n      return this.fillStyle = e;\n    }\n    this.context.setTextAlign = function(e) {\n      return this.textAlign = e;\n    }\n    this.context.draw = function() {}\n  }\n  //兼容NVUEsetLineDash\n  if(!this.context.setLineDash){\n    this.context.setLineDash = function(e) {}\n  }\n  opts.chartData = {};\n  this.uevent = new uChartsEvent();\n  this.scrollOption = {\n    currentOffset: 0,\n    startTouchX: 0,\n    distance: 0,\n    lastMoveTime: 0\n  };\n  this.opts = opts;\n  this.config = config$$1;\n  drawCharts.call(this, opts.type, opts, config$$1, this.context);\n};\n\nuCharts.prototype.updateData = function() {\n  let data = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n  this.opts = assign({}, this.opts, data);\n  this.opts.updateData = true;\n  let scrollPosition = data.scrollPosition || 'current';\n  switch (scrollPosition) {\n    case 'current':\n      this.opts._scrollDistance_ = this.scrollOption.currentOffset;\n      break;\n    case 'left':\n      this.opts._scrollDistance_ = 0;\n      this.scrollOption = {\n        currentOffset: 0,\n        startTouchX: 0,\n        distance: 0,\n        lastMoveTime: 0\n      };\n      break;\n    case 'right':\n      let _calYAxisData = calYAxisData(this.opts.series, this.opts, this.config, this.context), yAxisWidth = _calYAxisData.yAxisWidth;\n      this.config.yAxisWidth = yAxisWidth;\n      let offsetLeft = 0;\n      let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config), xAxisPoints = _getXAxisPoints0.xAxisPoints,\n        startX = _getXAxisPoints0.startX,\n        endX = _getXAxisPoints0.endX,\n        eachSpacing = _getXAxisPoints0.eachSpacing;\n      let totalWidth = eachSpacing * (xAxisPoints.length - 1);\n      let screenWidth = endX - startX;\n      offsetLeft = screenWidth - totalWidth;\n      this.scrollOption = {\n        currentOffset: offsetLeft,\n        startTouchX: offsetLeft,\n        distance: 0,\n        lastMoveTime: 0\n      };\n      this.opts._scrollDistance_ = offsetLeft;\n      break;\n  }\n  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);\n};\n\nuCharts.prototype.zoom = function() {\n  var val = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.opts.xAxis.itemCount;\n  if (this.opts.enableScroll !== true) {\n    console.log('[uCharts] 请启用滚动条后使用')\n    return;\n  }\n  //当前屏幕中间点\n  let centerPoint = Math.round(Math.abs(this.scrollOption.currentOffset) / this.opts.chartData.eachSpacing) + Math.round(this.opts.xAxis.itemCount / 2);\n  this.opts.animation = false;\n  this.opts.xAxis.itemCount = val.itemCount;\n  //重新计算x轴偏移距离\n  let _calYAxisData = calYAxisData(this.opts.series, this.opts, this.config, this.context),\n    yAxisWidth = _calYAxisData.yAxisWidth;\n  this.config.yAxisWidth = yAxisWidth;\n  let offsetLeft = 0;\n  let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config),\n    xAxisPoints = _getXAxisPoints0.xAxisPoints,\n    startX = _getXAxisPoints0.startX,\n    endX = _getXAxisPoints0.endX,\n    eachSpacing = _getXAxisPoints0.eachSpacing;\n  let centerLeft = eachSpacing * centerPoint;\n  let screenWidth = endX - startX;\n  let MaxLeft = screenWidth - eachSpacing * (xAxisPoints.length - 1);\n  offsetLeft = screenWidth / 2 - centerLeft;\n  if (offsetLeft > 0) {\n    offsetLeft = 0;\n  }\n  if (offsetLeft < MaxLeft) {\n    offsetLeft = MaxLeft;\n  }\n  this.scrollOption = {\n    currentOffset: offsetLeft,\n    startTouchX: 0,\n    distance: 0,\n    lastMoveTime: 0\n  };\n  calValidDistance(this, offsetLeft, this.opts.chartData, this.config, this.opts);\n  this.opts._scrollDistance_ = offsetLeft;\n  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);\n};\n\nuCharts.prototype.dobuleZoom = function(e) {\n  if (this.opts.enableScroll !== true) {\n    console.log('[uCharts] 请启用滚动条后使用')\n    return;\n  }\n  const tcs = e.changedTouches;\n  if (tcs.length < 2) {\n    return;\n  }\n  for (var i = 0; i < tcs.length; i++) {\n    tcs[i].x = tcs[i].x ? tcs[i].x : tcs[i].clientX;\n    tcs[i].y = tcs[i].y ? tcs[i].y : tcs[i].clientY;\n  }\n  const ntcs = [getTouches(tcs[0], this.opts, e),getTouches(tcs[1], this.opts, e)]; \n  const xlength = Math.abs(ntcs[0].x - ntcs[1].x);\n  // 记录初始的两指之间的数据\n  if(!this.scrollOption.moveCount){\n    let cts0 = {changedTouches:[{x:tcs[0].x,y:this.opts.area[0] / this.opts.pix + 2}]};\n    let cts1 = {changedTouches:[{x:tcs[1].x,y:this.opts.area[0] / this.opts.pix + 2}]};\n    if(this.opts.rotate){\n      cts0 = {changedTouches:[{x:this.opts.height / this.opts.pix - this.opts.area[0] / this.opts.pix - 2,y:tcs[0].y}]};\n      cts1 = {changedTouches:[{x:this.opts.height / this.opts.pix - this.opts.area[0] / this.opts.pix - 2,y:tcs[1].y}]};\n    }\n    const moveCurrent1 = this.getCurrentDataIndex(cts0).index;\n    const moveCurrent2 = this.getCurrentDataIndex(cts1).index;\n    const moveCount = Math.abs(moveCurrent1 - moveCurrent2);\n    this.scrollOption.moveCount = moveCount;\n    this.scrollOption.moveCurrent1 = Math.min(moveCurrent1, moveCurrent2);\n    this.scrollOption.moveCurrent2 = Math.max(moveCurrent1, moveCurrent2);\n    return;\n  }\n  \n  let currentEachSpacing = xlength / this.scrollOption.moveCount;\n  let itemCount = (this.opts.width - this.opts.area[1] - this.opts.area[3]) / currentEachSpacing;\n  itemCount = itemCount <= 2 ? 2 : itemCount;\n  itemCount = itemCount >= this.opts.categories.length ? this.opts.categories.length : itemCount;\n  this.opts.animation = false;\n  this.opts.xAxis.itemCount = itemCount;\n  // 重新计算滚动条偏移距离\n  let offsetLeft = 0;\n  let _getXAxisPoints0 = getXAxisPoints(this.opts.categories, this.opts, this.config),\n    xAxisPoints = _getXAxisPoints0.xAxisPoints,\n    startX = _getXAxisPoints0.startX,\n    endX = _getXAxisPoints0.endX,\n    eachSpacing = _getXAxisPoints0.eachSpacing;\n  let currentLeft = eachSpacing * this.scrollOption.moveCurrent1;\n  let screenWidth = endX - startX;\n  let MaxLeft = screenWidth - eachSpacing * (xAxisPoints.length - 1);\n  offsetLeft = -currentLeft+Math.min(ntcs[0].x,ntcs[1].x)-this.opts.area[3]-eachSpacing;\n  if (offsetLeft > 0) {\n    offsetLeft = 0;\n  }\n  if (offsetLeft < MaxLeft) {\n    offsetLeft = MaxLeft;\n  }\n  this.scrollOption.currentOffset= offsetLeft;\n  this.scrollOption.startTouchX= 0;\n  this.scrollOption.distance=0;\n  calValidDistance(this, offsetLeft, this.opts.chartData, this.config, this.opts);\n  this.opts._scrollDistance_ = offsetLeft;\n  drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);\n}\n\nuCharts.prototype.stopAnimation = function() {\n  this.animationInstance && this.animationInstance.stop();\n};\n\nuCharts.prototype.addEventListener = function(type, listener) {\n  this.uevent.addEventListener(type, listener);\n};\n\nuCharts.prototype.delEventListener = function(type) {\n  this.uevent.delEventListener(type);\n};\n\nuCharts.prototype.getCurrentDataIndex = function(e) {\n  var touches = null;\n  if (e.changedTouches) {\n    touches = e.changedTouches[0];\n  } else {\n    touches = e.mp.changedTouches[0];\n  }\n  if (touches) {\n    let _touches$ = getTouches(touches, this.opts, e);\n    if (this.opts.type === 'pie' || this.opts.type === 'ring') {\n      return findPieChartCurrentIndex({\n        x: _touches$.x,\n        y: _touches$.y\n      }, this.opts.chartData.pieData, this.opts);\n    } else if (this.opts.type === 'rose') {\n      return findRoseChartCurrentIndex({\n        x: _touches$.x,\n        y: _touches$.y\n      }, this.opts.chartData.pieData, this.opts);\n    } else if (this.opts.type === 'radar') {\n      return findRadarChartCurrentIndex({\n        x: _touches$.x,\n        y: _touches$.y\n      }, this.opts.chartData.radarData, this.opts.categories.length);\n    } else if (this.opts.type === 'funnel') {\n      return findFunnelChartCurrentIndex({\n        x: _touches$.x,\n        y: _touches$.y\n      }, this.opts.chartData.funnelData);\n    } else if (this.opts.type === 'map') {\n      return findMapChartCurrentIndex({\n        x: _touches$.x,\n        y: _touches$.y\n      }, this.opts);\n    } else if (this.opts.type === 'word') {\n      return findWordChartCurrentIndex({\n        x: _touches$.x,\n        y: _touches$.y\n      }, this.opts.chartData.wordCloudData);\n    } else if (this.opts.type === 'bar') {\n      return findBarChartCurrentIndex({\n        x: _touches$.x,\n        y: _touches$.y\n      }, this.opts.chartData.calPoints, this.opts, this.config, Math.abs(this.scrollOption.currentOffset));\n    } else {\n      return findCurrentIndex({\n        x: _touches$.x,\n        y: _touches$.y\n      }, this.opts.chartData.calPoints, this.opts, this.config, Math.abs(this.scrollOption.currentOffset));\n    }\n  }\n  return -1;\n};\n\nuCharts.prototype.getLegendDataIndex = function(e) {\n  var touches = null;\n  if (e.changedTouches) {\n    touches = e.changedTouches[0];\n  } else {\n    touches = e.mp.changedTouches[0];\n  }\n  if (touches) {\n    let _touches$ = getTouches(touches, this.opts, e);\n    return findLegendIndex({\n      x: _touches$.x,\n      y: _touches$.y\n    }, this.opts.chartData.legendData);\n  }\n  return -1;\n};\n\nuCharts.prototype.touchLegend = function(e) {\n  var option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n  var touches = null;\n  if (e.changedTouches) {\n    touches = e.changedTouches[0];\n  } else {\n    touches = e.mp.changedTouches[0];\n  }\n  if (touches) {\n    var _touches$ = getTouches(touches, this.opts, e);\n    var index = this.getLegendDataIndex(e);\n    if (index >= 0) {\n      if (this.opts.type == 'candle') {\n        this.opts.seriesMA[index].show = !this.opts.seriesMA[index].show;\n      } else {\n        this.opts.series[index].show = !this.opts.series[index].show;\n      }\n      this.opts.animation = option.animation ? true : false;\n      this.opts._scrollDistance_ = this.scrollOption.currentOffset;\n      drawCharts.call(this, this.opts.type, this.opts, this.config, this.context);\n    }\n  }\n\n};\n\nuCharts.prototype.showToolTip = function(e) {\n  var option = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n  var touches = null;\n  if (e.changedTouches) {\n    touches = e.changedTouches[0];\n  } else {\n    touches = e.mp.changedTouches[0];\n  }\n  if (!touches) {\n    console.log(\"[uCharts] 未获取到event坐标信息\");\n  }\n  var _touches$ = getTouches(touches, this.opts, e);\n  var currentOffset = this.scrollOption.currentOffset;\n  var opts = assign({}, this.opts, {\n    _scrollDistance_: currentOffset,\n    animation: false\n  });\n  if (this.opts.type === 'line' || this.opts.type === 'area' || this.opts.type === 'column' || this.opts.type === 'scatter' || this.opts.type === 'bubble') {\n    var current = this.getCurrentDataIndex(e);\n    var index = option.index == undefined ? current.index : option.index;\n    if (index > -1 || index.length>0) {\n      var seriesData = getSeriesDataItem(this.opts.series, index, current.group);\n      if (seriesData.length !== 0) {\n        var _getToolTipData = getToolTipData(seriesData, this.opts, index, current.group, this.opts.categories, option),\n          textList = _getToolTipData.textList,\n          offset = _getToolTipData.offset;\n        offset.y = _touches$.y;\n        opts.tooltip = {\n          textList: option.textList !== undefined ? option.textList : textList,\n          offset: option.offset !== undefined ? option.offset : offset,\n          option: option,\n          index: index\n        };\n      }\n    }\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n  if (this.opts.type === 'mount') {\n    var index = option.index == undefined ? this.getCurrentDataIndex(e).index : option.index;\n    if (index > -1) {\n      var opts = assign({}, this.opts, {animation: false});\n      var seriesData = assign({}, opts._series_[index]);\n      var textList = [{\n        text: option.formatter ? option.formatter(seriesData, undefined, index, opts) : seriesData.name + ': ' + seriesData.data,\n        color: seriesData.color\n      }];\n      var offset = {\n        x: opts.chartData.calPoints[index].x,\n        y: _touches$.y\n      };\n      opts.tooltip = {\n        textList: option.textList ? option.textList : textList,\n        offset: option.offset !== undefined ? option.offset : offset,\n        option: option,\n        index: index\n      };\n    }\n    \n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n  if (this.opts.type === 'bar') {\n    var current = this.getCurrentDataIndex(e);\n    var index = option.index == undefined ? current.index : option.index;\n    if (index > -1 || index.length>0) {\n      var seriesData = getSeriesDataItem(this.opts.series, index, current.group);\n      if (seriesData.length !== 0) {\n        var _getToolTipData = getToolTipData(seriesData, this.opts, index, current.group, this.opts.categories, option),\n          textList = _getToolTipData.textList,\n          offset = _getToolTipData.offset;\n        offset.x = _touches$.x;\n        opts.tooltip = {\n          textList: option.textList !== undefined ? option.textList : textList,\n          offset: option.offset !== undefined ? option.offset : offset,\n          option: option,\n          index: index\n        };\n      }\n    }\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n  if (this.opts.type === 'mix') {\n    var current = this.getCurrentDataIndex(e);\n    var index = option.index == undefined ? current.index : option.index;\n    if (index > -1) {\n      var currentOffset = this.scrollOption.currentOffset;\n      var opts = assign({}, this.opts, {\n        _scrollDistance_: currentOffset,\n        animation: false\n      });\n      var seriesData = getSeriesDataItem(this.opts.series, index);\n      if (seriesData.length !== 0) {\n        var _getMixToolTipData = getMixToolTipData(seriesData, this.opts, index, this.opts.categories, option),\n          textList = _getMixToolTipData.textList,\n          offset = _getMixToolTipData.offset;\n        offset.y = _touches$.y;\n        opts.tooltip = {\n          textList: option.textList ? option.textList : textList,\n          offset: option.offset !== undefined ? option.offset : offset,\n          option: option,\n          index: index\n        };\n      }\n    }\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n  if (this.opts.type === 'candle') {\n    var current = this.getCurrentDataIndex(e);\n    var index = option.index == undefined ? current.index : option.index;\n    if (index > -1) {\n      var currentOffset = this.scrollOption.currentOffset;\n      var opts = assign({}, this.opts, {\n        _scrollDistance_: currentOffset,\n        animation: false\n      });\n      var seriesData = getSeriesDataItem(this.opts.series, index);\n      if (seriesData.length !== 0) {\n        var _getToolTipData = getCandleToolTipData(this.opts.series[0].data, seriesData, this.opts, index, this.opts.categories, this.opts.extra.candle, option),\n          textList = _getToolTipData.textList,\n          offset = _getToolTipData.offset;\n        offset.y = _touches$.y;\n        opts.tooltip = {\n          textList: option.textList ? option.textList : textList,\n          offset: option.offset !== undefined ? option.offset : offset,\n          option: option,\n          index: index\n        };\n      }\n    }\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n  if (this.opts.type === 'pie' || this.opts.type === 'ring' || this.opts.type === 'rose' || this.opts.type === 'funnel') {\n    var index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;\n    if (index > -1) {\n      var opts = assign({}, this.opts, {animation: false});\n      var seriesData = assign({}, opts._series_[index]);\n      var textList = [{\n        text: option.formatter ? option.formatter(seriesData, undefined, index, opts) : seriesData.name + ': ' + seriesData.data,\n        color: seriesData.color\n      }];\n      var offset = {\n        x: _touches$.x,\n        y: _touches$.y\n      };\n      opts.tooltip = {\n        textList: option.textList ? option.textList : textList,\n        offset: option.offset !== undefined ? option.offset : offset,\n        option: option,\n        index: index\n      };\n    }\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n  if (this.opts.type === 'map') {\n    var index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;\n    if (index > -1) {\n      var opts = assign({}, this.opts, {animation: false});\n      var seriesData = assign({}, this.opts.series[index]);\n      seriesData.name = seriesData.properties.name\n      var textList = [{\n        text: option.formatter ? option.formatter(seriesData, undefined, index, this.opts) : seriesData.name,\n        color: seriesData.color\n      }];\n      var offset = {\n        x: _touches$.x,\n        y: _touches$.y\n      };\n      opts.tooltip = {\n        textList: option.textList ? option.textList : textList,\n        offset: option.offset !== undefined ? option.offset : offset,\n        option: option,\n        index: index\n      };\n    }\n    opts.updateData = false;\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n  if (this.opts.type === 'word') {\n    var index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;\n    if (index > -1) {\n      var opts = assign({}, this.opts, {animation: false});\n      var seriesData = assign({}, this.opts.series[index]);\n      var textList = [{\n        text: option.formatter ? option.formatter(seriesData, undefined, index, this.opts) : seriesData.name,\n        color: seriesData.color\n      }];\n      var offset = {\n        x: _touches$.x,\n        y: _touches$.y\n      };\n      opts.tooltip = {\n        textList: option.textList ? option.textList : textList,\n        offset: option.offset !== undefined ? option.offset : offset,\n        option: option,\n        index: index\n      };\n    }\n    opts.updateData = false;\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n  if (this.opts.type === 'radar') {\n    var index = option.index == undefined ? this.getCurrentDataIndex(e) : option.index;\n    if (index > -1) {\n      var opts = assign({}, this.opts, {animation: false});\n      var seriesData = getSeriesDataItem(this.opts.series, index);\n      if (seriesData.length !== 0) {\n        var textList = seriesData.map((item) => {\n          return {\n            text: option.formatter ? option.formatter(item, this.opts.categories[index], index, this.opts) : item.name + ': ' + item.data,\n            color: item.color\n          };\n        });\n        var offset = {\n          x: _touches$.x,\n          y: _touches$.y\n        };\n        opts.tooltip = {\n          textList: option.textList ? option.textList : textList,\n          offset: option.offset !== undefined ? option.offset : offset,\n          option: option,\n          index: index\n        };\n      }\n    }\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n  }\n};\n\nuCharts.prototype.translate = function(distance) {\n  this.scrollOption = {\n    currentOffset: distance,\n    startTouchX: distance,\n    distance: 0,\n    lastMoveTime: 0\n  };\n  let opts = assign({}, this.opts, {\n    _scrollDistance_: distance,\n    animation: false\n  });\n  drawCharts.call(this, this.opts.type, opts, this.config, this.context);\n};\n\nuCharts.prototype.scrollStart = function(e) {\n  var touches = null;\n  if (e.changedTouches) {\n    touches = e.changedTouches[0];\n  } else {\n    touches = e.mp.changedTouches[0];\n  }\n  var _touches$ = getTouches(touches, this.opts, e);\n  if (touches && this.opts.enableScroll === true) {\n    this.scrollOption.startTouchX = _touches$.x;\n  }\n};\n\nuCharts.prototype.scroll = function(e) {\n  if (this.scrollOption.lastMoveTime === 0) {\n    this.scrollOption.lastMoveTime = Date.now();\n  }\n  let Limit = this.opts.touchMoveLimit || 60;\n  let currMoveTime = Date.now();\n  let duration = currMoveTime - this.scrollOption.lastMoveTime;\n  if (duration < Math.floor(1000 / Limit)) return;\n  if (this.scrollOption.startTouchX == 0) return;\n  this.scrollOption.lastMoveTime = currMoveTime;\n  var touches = null;\n  if (e.changedTouches) {\n    touches = e.changedTouches[0];\n  } else {\n    touches = e.mp.changedTouches[0];\n  }\n  if (touches && this.opts.enableScroll === true) {\n    var _touches$ = getTouches(touches, this.opts, e);\n    var _distance;\n    _distance = _touches$.x - this.scrollOption.startTouchX;\n    var currentOffset = this.scrollOption.currentOffset;\n    var validDistance = calValidDistance(this, currentOffset + _distance, this.opts.chartData, this.config, this.opts);\n    this.scrollOption.distance = _distance = validDistance - currentOffset;\n    var opts = assign({}, this.opts, {\n      _scrollDistance_: currentOffset + _distance,\n      animation: false\n    });\n\t\tthis.opts = opts;\n    drawCharts.call(this, opts.type, opts, this.config, this.context);\n    return currentOffset + _distance;\n  }\n};\n\nuCharts.prototype.scrollEnd = function(e) {\n  if (this.opts.enableScroll === true) {\n    var _scrollOption = this.scrollOption,\n      currentOffset = _scrollOption.currentOffset,\n      distance = _scrollOption.distance;\n    this.scrollOption.currentOffset = currentOffset + distance;\n    this.scrollOption.distance = 0;\n    this.scrollOption.moveCount = 0;\n  }\n};\n\nexport default uCharts;"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/license.md",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\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       http://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"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/package.json",
    "content": "{\n  \"id\": \"qiun-data-charts\",\n  \"displayName\": \"秋云 ucharts echarts 高性能跨全端图表组件\",\n  \"version\": \"2.4.3-20220505\",\n  \"description\": \"uCharts 新增双指缩放、新增山峰图！支持H5及APP用 ucharts echarts 渲染图表，uniapp可视化首选组件\",\n  \"keywords\": [\n    \"ucharts\",\n    \"echarts\",\n    \"f2\",\n    \"图表\",\n    \"可视化\"\n],\n  \"repository\": \"https://gitee.com/uCharts/uCharts\",\n  \"engines\": {\n    \"HBuilderX\": \"^3.3.8\"\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\": \"474119\"\n    },\n    \"declaration\": {\n      \"ads\": \"无\",\n      \"data\": \"插件不采集任何数据\",\n      \"permissions\": \"无\"\n    },\n    \"npmurl\": \"https://www.npmjs.com/~qiun\"\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\": \"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          \"华为\": \"y\",\n          \"联盟\": \"y\"\n        },\n        \"Vue\": {\n            \"vue2\": \"y\",\n            \"vue3\": \"y\"\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/qiun-data-charts/readme.md",
    "content": "\n\n## <font color='red'>写给uCharts使用者的一封信</font>\n<font color='red'>\n亲爱的用户：\n\n- 由于最近上线的官网中实行了部分收费体验，收到了许多用户的使用反馈，大致反馈的问题都指向同一矛头：为何新官网的在线工具也要收费？对于这件事，我们深表歉意。由于新官网本身未提供技术文档，使得用户误以为我们对文档实行了收费。经我们连夜整改，新官网目前已经将技术文档开放出来供大家阅读使用，并免费对外开放了【演示】中的查看全端全平台的代码的功能，为此再次向所受影响的用户们致以诚恳的歉意。\n\n- 其次，我们须澄清几点，如下：\n1. uCharts的插件本身遵循开源原则，并不收费，用户可自行到DCloud市场与Gitee码云上获取源码\n2. uCharts的技术文档永久对用户开放\n3. 收费内容仅针对原生工具、组件工具、定制功能以及模板市场的部分收费模板\n\n- uCharts为什么实行收费原则？\n1. 服务器的费用支撑\n2. 团队的运营支出；正如你所见，我们的群里有大量的用户在请教图表配置与反馈问题，群里的每一位管理员都在花费不少精力在积极解决用户的问题，然而遇到巨大的咨询量时，我们无法及时、精准解答回复，因此，我们推出了会员优先服务\n3. 与其说模板市场是收费，倒不如说给野生用户提供了创造价值的机会，用户既可以在上面发布模板赚取费用，遇到心动的模板也能免费/付费使用\n\n- 收费不是目的，正如你们所见，用户可以申请成为[【开发者】](https://www.ucharts.cn/v2/#/agreement/developer)，开发者不限制任何官网功能，并享有官方指导、开发、改造uCharts的权力，并且活动期间【返还超级会员费用】！我们想说的是，我们新版官网上线旨在希望更多的用户加入到开发者的队伍，我们共同去维护uCharts！\n       \n我们相信：星星之火可以燎原！\n\nuCharts技术团队\n\n2022.4.23\n\n</font>\n\n\n![logo](https://img-blog.csdnimg.cn/4a276226973841468c1be356f8d9438b.png)\n\n\n[![star](https://gitee.com/uCharts/uCharts/badge/star.svg?theme=gvp)](https://gitee.com/uCharts/uCharts/stargazers)\n[![fork](https://gitee.com/uCharts/uCharts/badge/fork.svg?theme=gvp)](https://gitee.com/uCharts/uCharts/members)\n[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)\n[![npm package](https://img.shields.io/npm/v/@qiun/ucharts.svg?style=flat-square)](https://www.npmjs.com/~qiun)\n\n\n## uCharts简介\n\n`uCharts`是一款基于`canvas API`开发的适用于所有前端应用的图表库，开发者编写一套代码，可运行到 Web、iOS、Android（基于 uni-app / taro ）、以及各种小程序（微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝）、快应用等更多支持 canvas API 的平台。\n\n## 官方网站\n\n## [https://www.ucharts.cn](https://www.ucharts.cn)\n\n## 快速体验\n\n一套代码编到多个平台，依次扫描二维码，亲自体验uCharts图表跨平台效果！其他平台请自行编译。\n\n![](https://www.ucharts.cn/images/web/guide/qrcode20220224.png)\n\n## 致开发者\n\n感谢各位开发者`四年`来对秋云及uCharts的支持，uCharts的进步离不开各位开发者的鼓励与贡献。为更好的帮助各位开发者使用图表工具，我们推出了新版官网，增加了在线定制、问答社区、在线配置等一些增值服务，为确保您能更好的应用图表组件，建议您先`仔细阅读本页指南`以及`常见问题`，而不是下载下来`直接使用`。如仍然不能解决，请到`官网社区`或开通会员后加入`专属VIP会员群`提问将会很快得到回答。\n\n## 社群支持\n\nuCharts官方拥有4个2000人的QQ群及专属VIP会员群支持，庞大的用户量证明我们一直在努力，请各位放心使用！uCharts的开源图表组件的开发，团队付出了大量的时间与精力，经过四来的考验，不会有比较明显的bug，请各位放心使用。如果您有更好的想法，可以在`码云提交Pull Requests`以帮助更多开发者完成需求，再次感谢各位对uCharts的鼓励与支持！\n\n#### 官方交流群\n- 交流群1：371774600（已满）\n- 交流群2：619841586（已满）\n- 交流群3：955340127（已满）\n- 交流群4：641669795\n- 口令`uniapp`\n\n#### 专属VIP会员群\n- 开通会员后详见【账号详情】页面中顶部的滚动通知\n- 口令`您的用户ID`\n\n## 版权信息\n\nuCharts始终坚持开源，遵循 [Apache Licence 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) 开源协议，意味着您无需支付任何费用，即可将uCharts应用到您的产品中。\n\n注意：这并不意味着您可以将uCharts应用到非法的领域，比如涉及赌博，暴力等方面。如因此产生纠纷或法律问题，uCharts相关方及秋云科技不承担任何责任。\n\n## 合作伙伴\n\n[![DIY官网](https://www.ucharts.cn/images/web/guide/links/diy-gw.png)](https://www.diygw.com/)\n[![HasChat](https://www.ucharts.cn/images/web/guide/links/haschat.png)](https://gitee.com/howcode/has-chat)\n[![uViewUI](https://www.ucharts.cn/images/web/guide/links/uView.png)](https://www.uviewui.com/)\n[![图鸟UI](https://www.ucharts.cn/images/web/guide/links/tuniao.png)](https://ext.dcloud.net.cn/plugin?id=7088)\n[![thorui](https://www.ucharts.cn/images/web/guide/links/thorui.png)](https://ext.dcloud.net.cn/publisher?id=202)\n[![FirstUI](https://www.ucharts.cn/images/web/guide/links/first.png)](https://www.firstui.cn/)\n[![nProUI](https://www.ucharts.cn/images/web/guide/links/nPro.png)](https://ext.dcloud.net.cn/plugin?id=5169)\n[![GraceUI](https://www.ucharts.cn/images/web/guide/links/grace.png)](https://www.graceui.com/)\n\n\n## 更新记录\n\n详见官网指南中说明，[点击此处查看](https://www.ucharts.cn/v2/#/guide/index?id=100)\n\n\n## 相关链接\n- [uCharts官网](https://www.ucharts.cn)\n- [DCloud插件市场地址](https://ext.dcloud.net.cn/plugin?id=271)\n- [uCharts码云开源托管地址](https://gitee.com/uCharts/uCharts) [![star](https://gitee.com/uCharts/uCharts/badge/star.svg?theme=gvp)](https://gitee.com/uCharts/uCharts/stargazers)\n- [uCharts npm开源地址](https://www.ucharts.cn)\n- [ECharts官网](https://echarts.apache.org/zh/index.html)\n- [ECharts配置手册](https://echarts.apache.org/zh/option.html)\n- [图表组件在项目中的应用 ReportPlus数据报表](https://www.ucharts.cn/v2/#/layout/info?id=1) "
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/changelog.md",
    "content": "## 2.2.10（2022-09-19）\n- 修复，反向选择日期范围，日期显示异常，[详情](https://ask.dcloud.net.cn/question/153401?item_id=212892&rf=false)\n## 2.2.9（2022-09-16）\n- 可以使用 uni-scss 控制主题色\n## 2.2.8（2022-09-08）\n- 修复 close事件无效的 bug\n## 2.2.7（2022-09-05）\n- 修复 移动端 maskClick 无效的 bug，详见:[https://ask.dcloud.net.cn/question/140824?item_id=209458&rf=false](https://ask.dcloud.net.cn/question/140824?item_id=209458&rf=false)\n## 2.2.6（2022-06-30）\n- 优化 组件样式，调整了组件图标大小、高度、颜色等，与uni-ui风格保持一致\n## 2.2.5（2022-06-24）\n- 修复 日历顶部年月及底部确认未国际化 bug\n## 2.2.4（2022-03-31）\n- 修复 Vue3 下动态赋值,单选类型未响应的 bug\n## 2.2.3（2022-03-28）\n- 修复 Vue3 下动态赋值未响应的 bug\n## 2.2.2（2021-12-10）\n- 修复 clear-icon 属性在小程序平台不生效的 bug\n## 2.2.1（2021-12-10）\n- 修复 日期范围选在小程序平台，必须多点击一次才能取消选中状态的 bug\n## 2.2.0（2021-11-19）\n- 优化 组件UI，并提供设计资源，详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)\n- 文档迁移，详见:[https://uniapp.dcloud.io/component/uniui/uni-datetime-picker](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)\n## 2.1.5（2021-11-09） \n- 新增 提供组件设计资源，组件样式调整\n## 2.1.4（2021-09-10）\n- 修复 hide-second 在移动端的 bug\n- 修复 单选赋默认值时，赋值日期未高亮的 bug\n- 修复 赋默认值时，移动端未正确显示时间的 bug\n## 2.1.3（2021-09-09）\n- 新增 hide-second 属性，支持只使用时分，隐藏秒\n## 2.1.2（2021-09-03）\n- 优化 取消选中时（范围选）直接开始下一次选择, 避免多点一次\n- 优化 移动端支持清除按钮，同时支持通过 ref 调用组件的 clear 方法\n- 优化 调整字号大小，美化日历界面\n- 修复 因国际化导致的 placeholder 失效的 bug\n## 2.1.1（2021-08-24）\n- 新增 支持国际化\n- 优化 范围选择器在 pc 端过宽的问题\n## 2.1.0（2021-08-09）\n- 新增 适配 vue3\n## 2.0.19（2021-08-09）\n- 新增 支持作为 uni-forms 子组件相关功能\n- 修复 在 uni-forms 中使用时，选择时间报 NAN 错误的 bug\n## 2.0.18（2021-08-05）\n- 修复 type 属性动态赋值无效的 bug\n- 修复 ‘确认’按钮被 tabbar 遮盖 bug\n- 修复 组件未赋值时范围选左、右日历相同的 bug\n## 2.0.17（2021-08-04）\n- 修复 范围选未正确显示当前值的 bug\n- 修复 h5 平台（移动端）报错 'cale' of undefined 的 bug\n## 2.0.16（2021-07-21）\n- 新增 return-type 属性支持返回 date 日期对象\n## 2.0.15（2021-07-14）\n- 修复 单选日期类型，初始赋值后不在当前日历的 bug\n- 新增 clearIcon 属性，显示框的清空按钮可配置显示隐藏（仅 pc 有效）\n- 优化 移动端移除显示框的清空按钮，无实际用途\n## 2.0.14（2021-07-14）\n- 修复 组件赋值为空，界面未更新的 bug\n- 修复 start 和 end 不能动态赋值的 bug\n- 修复 范围选类型，用户选择后再次选择右侧日历（结束日期）显示不正确的 bug\n## 2.0.13（2021-07-08）\n- 修复 范围选择不能动态赋值的 bug\n## 2.0.12（2021-07-08）\n- 修复 范围选择的初始时间在一个月内时，造成无法选择的bug\n## 2.0.11（2021-07-08）\n- 优化 弹出层在超出视窗边缘定位不准确的问题\n## 2.0.10（2021-07-08）\n- 修复 范围起始点样式的背景色与今日样式的字体前景色融合，导致日期字体看不清的 bug\n- 优化 弹出层在超出视窗边缘被遮盖的问题\n## 2.0.9（2021-07-07）\n- 新增 maskClick 事件\n- 修复 特殊情况日历 rpx 布局错误的 bug，rpx -> px\n- 修复 范围选择时清空返回值不合理的bug，['', ''] -> []\n## 2.0.8（2021-07-07）\n- 新增 日期时间显示框支持插槽\n## 2.0.7（2021-07-01）\n- 优化 添加 uni-icons 依赖\n## 2.0.6（2021-05-22）\n- 修复 图标在小程序上不显示的 bug\n- 优化 重命名引用组件，避免潜在组件命名冲突\n## 2.0.5（2021-05-20）\n- 优化 代码目录扁平化\n## 2.0.4（2021-05-12）\n- 新增 组件示例地址\n## 2.0.3（2021-05-10）\n- 修复 ios 下不识别 '-' 日期格式的 bug\n- 优化 pc 下弹出层添加边框和阴影\n## 2.0.2（2021-05-08）\n- 修复 在 admin 中获取弹出层定位错误的bug\n## 2.0.1（2021-05-08）\n- 修复 type 属性向下兼容，默认值从 date 变更为 datetime\n## 2.0.0（2021-04-30）\n- 支持日历形式的日期+时间的范围选择\n > 注意：此版本不向后兼容，不再支持单独时间选择（type=time）及相关的 hide-second 属性（时间选可使用内置组件 picker）\n## 1.0.6（2021-03-18）\n- 新增 hide-second 属性，时间支持仅选择时、分\n- 修复 选择跟显示的日期不一样的 bug\n- 修复 chang事件触发2次的 bug\n- 修复 分、秒 end 范围错误的 bug\n- 优化 更好的 nvue 适配\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue",
    "content": "<template>\n\t<view class=\"uni-calendar-item__weeks-box\" :class=\"{\n\t\t'uni-calendar-item--disable':weeks.disable,\n\t\t'uni-calendar-item--before-checked-x':weeks.beforeMultiple,\n\t\t'uni-calendar-item--multiple': weeks.multiple,\n\t\t'uni-calendar-item--after-checked-x':weeks.afterMultiple,\n\t\t}\" @click=\"choiceDate(weeks)\" @mouseenter=\"handleMousemove(weeks)\">\n\t\t<view class=\"uni-calendar-item__weeks-box-item\" :class=\"{\n\t\t\t\t'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && (calendar.userChecked || !checkHover),\n\t\t\t\t'uni-calendar-item--checked-range-text': checkHover,\n\t\t\t\t'uni-calendar-item--before-checked':weeks.beforeMultiple,\n\t\t\t\t'uni-calendar-item--multiple': weeks.multiple,\n\t\t\t\t'uni-calendar-item--after-checked':weeks.afterMultiple,\n\t\t\t\t'uni-calendar-item--disable':weeks.disable,\n\t\t\t\t}\">\n\t\t\t<text v-if=\"selected&&weeks.extraInfo\" class=\"uni-calendar-item__weeks-box-circle\"></text>\n\t\t\t<text class=\"uni-calendar-item__weeks-box-text uni-calendar-item__weeks-box-text-disable uni-calendar-item--checked-text\">{{weeks.date}}</text>\n\t\t</view>\n\t\t<view :class=\"{'uni-calendar-item--isDay': weeks.isDay}\"></view>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tprops: {\n\t\t\tweeks: {\n\t\t\t\ttype: Object,\n\t\t\t\tdefault () {\n\t\t\t\t\treturn {}\n\t\t\t\t}\n\t\t\t},\n\t\t\tcalendar: {\n\t\t\t\ttype: Object,\n\t\t\t\tdefault: () => {\n\t\t\t\t\treturn {}\n\t\t\t\t}\n\t\t\t},\n\t\t\tselected: {\n\t\t\t\ttype: Array,\n\t\t\t\tdefault: () => {\n\t\t\t\t\treturn []\n\t\t\t\t}\n\t\t\t},\n\t\t\tlunar: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tcheckHover: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tchoiceDate(weeks) {\n\t\t\t\tthis.$emit('change', weeks)\n\t\t\t},\n\t\t\thandleMousemove(weeks) {\n\t\t\t\tthis.$emit('handleMouse', weeks)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" >\n\t$uni-primary: #007aff !default;\n\n\t.uni-calendar-item__weeks-box {\n\t\tflex: 1;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tmargin: 1px 0;\n\t\tposition: relative;\n\t}\n\n\t.uni-calendar-item__weeks-box-text {\n\t\tfont-size: 14px;\n\t\t// font-family: Lato-Bold, Lato;\n\t\tfont-weight: bold;\n\t\tcolor: darken($color: $uni-primary, $amount: 40%);\n\t}\n\n\t.uni-calendar-item__weeks-lunar-text {\n\t\tfont-size: 12px;\n\t\tcolor: #333;\n\t}\n\n\t.uni-calendar-item__weeks-box-item {\n\t\tposition: relative;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\twidth: 40px;\n\t\theight: 40px;\n\t\t/* #ifdef H5 */\n\t\tcursor: pointer;\n\t\t/* #endif */\n\t}\n\n\n\t.uni-calendar-item__weeks-box-circle {\n\t\tposition: absolute;\n\t\ttop: 5px;\n\t\tright: 5px;\n\t\twidth: 8px;\n\t\theight: 8px;\n\t\tborder-radius: 8px;\n\t\tbackground-color: #dd524d;\n\n\t}\n\n\t.uni-calendar-item__weeks-box .uni-calendar-item--disable {\n\t\t// background-color: rgba(249, 249, 249, $uni-opacity-disabled);\n\t\tcursor: default;\n\t}\n\n\t.uni-calendar-item--disable .uni-calendar-item__weeks-box-text-disable {\n\t\tcolor: #D1D1D1;\n\t}\n\n\t.uni-calendar-item--isDay {\n\t\tposition: absolute;\n\t\ttop: 10px;\n\t\tright: 17%;\n\t\tbackground-color: #dd524d;\n\t\twidth:6px;\n\t\theight: 6px;\n\t\tborder-radius: 50%;\n\t}\n\n\t.uni-calendar-item--extra {\n\t\tcolor: #dd524d;\n\t\topacity: 0.8;\n\t}\n\n\t.uni-calendar-item__weeks-box .uni-calendar-item--checked {\n\t\tbackground-color: $uni-primary;\n\t\tborder-radius: 50%;\n\t\tbox-sizing: border-box;\n\t\tborder: 3px solid #fff;\n\t}\n\n\t.uni-calendar-item--checked .uni-calendar-item--checked-text {\n\t\tcolor: #fff;\n\t}\n\n\t.uni-calendar-item--multiple .uni-calendar-item--checked-range-text {\n\t\tcolor: #333;\n\t}\n\n\t.uni-calendar-item--multiple {\n\t\tbackground-color:  #F6F7FC;\n\t\t// color: #fff;\n\t}\n\n\t.uni-calendar-item--multiple .uni-calendar-item--before-checked,\n\t.uni-calendar-item--multiple .uni-calendar-item--after-checked {\n\t\tbackground-color: $uni-primary;\n\t\tborder-radius: 50%;\n\t\tbox-sizing: border-box;\n\t\tborder: 3px solid #F6F7FC;\n\t}\n\n\t.uni-calendar-item--before-checked .uni-calendar-item--checked-text,\n\t.uni-calendar-item--after-checked .uni-calendar-item--checked-text {\n\t\tcolor: #fff;\n\t}\n\n\t.uni-calendar-item--before-checked-x {\n\t\tborder-top-left-radius: 50px;\n\t\tborder-bottom-left-radius: 50px;\n\t\tbox-sizing: border-box;\n\t\tbackground-color: #F6F7FC;\n\t}\n\n\t.uni-calendar-item--after-checked-x {\n\t\tborder-top-right-radius: 50px;\n\t\tborder-bottom-right-radius: 50px;\n\t\tbackground-color: #F6F7FC;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue",
    "content": "<template>\n\t<view class=\"uni-calendar\" @mouseleave=\"leaveCale\">\n\t\t<view v-if=\"!insert&&show\" class=\"uni-calendar__mask\" :class=\"{'uni-calendar--mask-show':aniMaskShow}\"\n\t\t\t@click=\"clean();maskClick()\"></view>\n\t\t<view v-if=\"insert || show\" class=\"uni-calendar__content\"\n\t\t\t:class=\"{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow, 'uni-calendar__content-mobile': aniMaskShow}\">\n\t\t\t<view class=\"uni-calendar__header\" :class=\"{'uni-calendar__header-mobile' :!insert}\">\n\t\t\t\t<view v-if=\"left\" class=\"uni-calendar__header-btn-box\" @click.stop=\"pre\">\n\t\t\t\t\t<view class=\"uni-calendar__header-btn uni-calendar--left\"></view>\n\t\t\t\t</view>\n\t\t\t\t<picker mode=\"date\" :value=\"date\" fields=\"month\" @change=\"bindDateChange\">\n\t\t\t\t\t<text\n\t\t\t\t\t\tclass=\"uni-calendar__header-text\">{{ (nowDate.year||'') + yearText + ( nowDate.month||'') + monthText}}</text>\n\t\t\t\t</picker>\n\t\t\t\t<view v-if=\"right\" class=\"uni-calendar__header-btn-box\" @click.stop=\"next\">\n\t\t\t\t\t<view class=\"uni-calendar__header-btn uni-calendar--right\"></view>\n\t\t\t\t</view>\n\t\t\t\t<view v-if=\"!insert\" class=\"dialog-close\" @click=\"clean\">\n\t\t\t\t\t<view class=\"dialog-close-plus\" data-id=\"close\"></view>\n\t\t\t\t\t<view class=\"dialog-close-plus dialog-close-rotate\" data-id=\"close\"></view>\n\t\t\t\t</view>\n\n\t\t\t\t<!-- <text class=\"uni-calendar__backtoday\" @click=\"backtoday\">回到今天</text> -->\n\t\t\t</view>\n\t\t\t<view class=\"uni-calendar__box\">\n\t\t\t\t<view v-if=\"showMonth\" class=\"uni-calendar__box-bg\">\n\t\t\t\t\t<text class=\"uni-calendar__box-bg-text\">{{nowDate.month}}</text>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"uni-calendar__weeks\" style=\"padding-bottom: 7px;\">\n\t\t\t\t\t<view class=\"uni-calendar__weeks-day\">\n\t\t\t\t\t\t<text class=\"uni-calendar__weeks-day-text\">{{SUNText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"uni-calendar__weeks-day\">\n\t\t\t\t\t\t<text class=\"uni-calendar__weeks-day-text\">{{MONText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"uni-calendar__weeks-day\">\n\t\t\t\t\t\t<text class=\"uni-calendar__weeks-day-text\">{{TUEText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"uni-calendar__weeks-day\">\n\t\t\t\t\t\t<text class=\"uni-calendar__weeks-day-text\">{{WEDText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"uni-calendar__weeks-day\">\n\t\t\t\t\t\t<text class=\"uni-calendar__weeks-day-text\">{{THUText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"uni-calendar__weeks-day\">\n\t\t\t\t\t\t<text class=\"uni-calendar__weeks-day-text\">{{FRIText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"uni-calendar__weeks-day\">\n\t\t\t\t\t\t<text class=\"uni-calendar__weeks-day-text\">{{SATText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"uni-calendar__weeks\" v-for=\"(item,weekIndex) in weeks\" :key=\"weekIndex\">\n\t\t\t\t\t<view class=\"uni-calendar__weeks-item\" v-for=\"(weeks,weeksIndex) in item\" :key=\"weeksIndex\">\n\t\t\t\t\t\t<calendar-item class=\"uni-calendar-item--hook\" :weeks=\"weeks\" :calendar=\"calendar\"\n\t\t\t\t\t\t\t:selected=\"selected\" :lunar=\"lunar\" :checkHover=\"range\" @change=\"choiceDate\"\n\t\t\t\t\t\t\t@handleMouse=\"handleMouse\">\n\t\t\t\t\t\t</calendar-item>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<view v-if=\"!insert && !range && typeHasTime\" class=\"uni-date-changed uni-calendar--fixed-top\"\n\t\t\t\tstyle=\"padding: 0 80px;\">\n\t\t\t\t<view class=\"uni-date-changed--time-date\">{{tempSingleDate ? tempSingleDate : selectDateText}}</view>\n\t\t\t\t<time-picker type=\"time\" :start=\"reactStartTime\" :end=\"reactEndTime\" v-model=\"time\"\n\t\t\t\t\t:disabled=\"!tempSingleDate\" :border=\"false\" :hide-second=\"hideSecond\" class=\"time-picker-style\">\n\t\t\t\t</time-picker>\n\t\t\t</view>\n\n\t\t\t<view v-if=\"!insert && range && typeHasTime\" class=\"uni-date-changed uni-calendar--fixed-top\">\n\t\t\t\t<view class=\"uni-date-changed--time-start\">\n\t\t\t\t\t<view class=\"uni-date-changed--time-date\">{{tempRange.before ? tempRange.before : startDateText}}\n\t\t\t\t\t</view>\n\t\t\t\t\t<time-picker type=\"time\" :start=\"reactStartTime\" v-model=\"timeRange.startTime\" :border=\"false\"\n\t\t\t\t\t\t:hide-second=\"hideSecond\" :disabled=\"!tempRange.before\" class=\"time-picker-style\">\n\t\t\t\t\t</time-picker>\n\t\t\t\t</view>\n\t\t\t\t<uni-icons type=\"arrowthinright\" color=\"#999\" style=\"line-height: 50px;\"></uni-icons>\n\t\t\t\t<view class=\"uni-date-changed--time-end\">\n\t\t\t\t\t<view class=\"uni-date-changed--time-date\">{{tempRange.after ? tempRange.after : endDateText}}</view>\n\t\t\t\t\t<time-picker type=\"time\" :end=\"reactEndTime\" v-model=\"timeRange.endTime\" :border=\"false\"\n\t\t\t\t\t\t:hide-second=\"hideSecond\" :disabled=\"!tempRange.after\" class=\"time-picker-style\">\n\t\t\t\t\t</time-picker>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<view v-if=\"!insert\" class=\"uni-date-changed uni-date-btn--ok\">\n\t\t\t\t<!-- <view class=\"uni-calendar__header-btn-box\">\n\t\t\t\t\t<text class=\"uni-calendar__button-text uni-calendar--fixed-width\">{{okText}}</text>\n\t\t\t\t</view> -->\n\t\t\t\t<view class=\"uni-datetime-picker--btn\" @click=\"confirm\">{{confirmText}}</view>\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport Calendar from './util.js';\n\timport calendarItem from './calendar-item.vue'\n\timport timePicker from './time-picker.vue'\n\timport {\n\t\tinitVueI18n\n\t} from '@dcloudio/uni-i18n'\n\timport messages from './i18n/index.js'\n\tconst {\n\t\tt\n\t} = initVueI18n(messages)\n\t/**\n\t * Calendar 日历\n\t * @description 日历组件可以查看日期，选择任意范围内的日期，打点操作。常用场景如：酒店日期预订、火车机票选择购买日期、上下班打卡等\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=56\n\t * @property {String} date 自定义当前时间，默认为今天\n\t * @property {Boolean} lunar 显示农历\n\t * @property {String} startDate 日期选择范围-开始日期\n\t * @property {String} endDate 日期选择范围-结束日期\n\t * @property {Boolean} range 范围选择\n\t * @property {Boolean} insert = [true|false] 插入模式,默认为false\n\t * \t@value true 弹窗模式\n\t * \t@value false 插入模式\n\t * @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容\n\t * @property {Array} selected 打点，期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]\n\t * @property {Boolean} showMonth 是否选择月份为背景\n\t * @event {Function} change 日期改变，`insert :ture` 时生效\n\t * @event {Function} confirm 确认选择`insert :false` 时生效\n\t * @event {Function} monthSwitch 切换月份时触发\n\t * @example <uni-calendar :insert=\"true\":lunar=\"true\" :start-date=\"'2019-3-2'\":end-date=\"'2019-5-20'\"@change=\"change\" />\n\t */\n\texport default {\n\t\tcomponents: {\n\t\t\tcalendarItem,\n\t\t\ttimePicker\n\t\t},\n\t\tprops: {\n\t\t\tdate: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tdefTime: {\n\t\t\t\ttype: [String, Object],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tselectableTimes: {\n\t\t\t\ttype: [Object],\n\t\t\t\tdefault () {\n\t\t\t\t\treturn {}\n\t\t\t\t}\n\t\t\t},\n\t\t\tselected: {\n\t\t\t\ttype: Array,\n\t\t\t\tdefault () {\n\t\t\t\t\treturn []\n\t\t\t\t}\n\t\t\t},\n\t\t\tlunar: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tstartDate: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tendDate: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\trange: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\ttypeHasTime: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tinsert: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\tshowMonth: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\tclearDate: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\tleft: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\tright: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\tcheckHover: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\thideSecond: {\n\t\t\t\ttype: [Boolean],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tpleStatus: {\n\t\t\t\ttype: Object,\n\t\t\t\tdefault () {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tbefore: '',\n\t\t\t\t\t\tafter: '',\n\t\t\t\t\t\tdata: [],\n\t\t\t\t\t\tfulldate: ''\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tshow: false,\n\t\t\t\tweeks: [],\n\t\t\t\tcalendar: {},\n\t\t\t\tnowDate: '',\n\t\t\t\taniMaskShow: false,\n\t\t\t\tfirstEnter: true,\n\t\t\t\ttime: '',\n\t\t\t\ttimeRange: {\n\t\t\t\t\tstartTime: '',\n\t\t\t\t\tendTime: ''\n\t\t\t\t},\n\t\t\t\ttempSingleDate: '',\n\t\t\t\ttempRange: {\n\t\t\t\t\tbefore: '',\n\t\t\t\t\tafter: ''\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tdate: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tif (!this.range) {\n\t\t\t\t\t\tthis.tempSingleDate = newVal\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tthis.init(newVal)\n\t\t\t\t\t\t}, 100)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tdefTime: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tif (!this.range) {\n\t\t\t\t\t\tthis.time = newVal\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// console.log('-----', newVal);\n\t\t\t\t\t\tthis.timeRange.startTime = newVal.start\n\t\t\t\t\t\tthis.timeRange.endTime = newVal.end\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tstartDate(val) {\n\t\t\t\tthis.cale.resetSatrtDate(val)\n\t\t\t\tthis.cale.setDate(this.nowDate.fullDate)\n\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t},\n\t\t\tendDate(val) {\n\t\t\t\tthis.cale.resetEndDate(val)\n\t\t\t\tthis.cale.setDate(this.nowDate.fullDate)\n\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t},\n\t\t\tselected(newVal) {\n\t\t\t\tthis.cale.setSelectInfo(this.nowDate.fullDate, newVal)\n\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t},\n\t\t\tpleStatus: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tbefore,\n\t\t\t\t\t\tafter,\n\t\t\t\t\t\tfulldate,\n\t\t\t\t\t\twhich\n\t\t\t\t\t} = newVal\n\t\t\t\t\tthis.tempRange.before = before\n\t\t\t\t\tthis.tempRange.after = after\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tif (fulldate) {\n\t\t\t\t\t\t\tthis.cale.setHoverMultiple(fulldate)\n\t\t\t\t\t\t\tif (before && after) {\n\t\t\t\t\t\t\t\tthis.cale.lastHover = true\n\t\t\t\t\t\t\t\tif (this.rangeWithinMonth(after, before)) return\n\t\t\t\t\t\t\t\tthis.setDate(before)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.cale.setMultiple(fulldate)\n\t\t\t\t\t\t\t\tthis.setDate(this.nowDate.fullDate)\n\t\t\t\t\t\t\t\tthis.calendar.fullDate = ''\n\t\t\t\t\t\t\t\tthis.cale.lastHover = false\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.cale.setDefaultMultiple(before, after)\n\t\t\t\t\t\t\tif (which === 'left') {\n\t\t\t\t\t\t\t\tthis.setDate(before)\n\t\t\t\t\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.setDate(after)\n\t\t\t\t\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tthis.cale.lastHover = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 16)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\treactStartTime() {\n\t\t\t\tconst activeDate = this.range ? this.tempRange.before : this.calendar.fullDate\n\t\t\t\tconst res = activeDate === this.startDate ? this.selectableTimes.start : ''\n\t\t\t\treturn res\n\t\t\t},\n\t\t\treactEndTime() {\n\t\t\t\tconst activeDate = this.range ? this.tempRange.after : this.calendar.fullDate\n\t\t\t\tconst res = activeDate === this.endDate ? this.selectableTimes.end : ''\n\t\t\t\treturn res\n\t\t\t},\n\t\t\t/**\n\t\t\t * for i18n\n\t\t\t */\n\t\t\tselectDateText() {\n\t\t\t\treturn t(\"uni-datetime-picker.selectDate\")\n\t\t\t},\n\t\t\tstartDateText() {\n\t\t\t\treturn this.startPlaceholder || t(\"uni-datetime-picker.startDate\")\n\t\t\t},\n\t\t\tendDateText() {\n\t\t\t\treturn this.endPlaceholder || t(\"uni-datetime-picker.endDate\")\n\t\t\t},\n\t\t\tokText() {\n\t\t\t\treturn t(\"uni-datetime-picker.ok\")\n\t\t\t},\n\t\t\tyearText() {\n\t\t\t\treturn t(\"uni-datetime-picker.year\")\n\t\t\t},\n\t\t\tmonthText() {\n\t\t\t\treturn t(\"uni-datetime-picker.month\")\n\t\t\t},\n\t\t\tMONText() {\n\t\t\t\treturn t(\"uni-calender.MON\")\n\t\t\t},\n\t\t\tTUEText() {\n\t\t\t\treturn t(\"uni-calender.TUE\")\n\t\t\t},\n\t\t\tWEDText() {\n\t\t\t\treturn t(\"uni-calender.WED\")\n\t\t\t},\n\t\t\tTHUText() {\n\t\t\t\treturn t(\"uni-calender.THU\")\n\t\t\t},\n\t\t\tFRIText() {\n\t\t\t\treturn t(\"uni-calender.FRI\")\n\t\t\t},\n\t\t\tSATText() {\n\t\t\t\treturn t(\"uni-calender.SAT\")\n\t\t\t},\n\t\t\tSUNText() {\n\t\t\t\treturn t(\"uni-calender.SUN\")\n\t\t\t},\n\t\t\tconfirmText() {\n\t\t\t\treturn t(\"uni-calender.confirm\")\n\t\t\t},\n\t\t},\n\t\tcreated() {\n\t\t\t// 获取日历方法实例\n\t\t\tthis.cale = new Calendar({\n\t\t\t\t// date: new Date(),\n\t\t\t\tselected: this.selected,\n\t\t\t\tstartDate: this.startDate,\n\t\t\t\tendDate: this.endDate,\n\t\t\t\trange: this.range,\n\t\t\t\t// multipleStatus: this.pleStatus\n\t\t\t})\n\t\t\t// 选中某一天\n\t\t\t// this.cale.setDate(this.date)\n\t\t\tthis.init(this.date)\n\t\t\t// this.setDay\n\t\t},\n\t\tmethods: {\n\t\t\tleaveCale() {\n\t\t\t\tthis.firstEnter = true\n\t\t\t},\n\t\t\thandleMouse(weeks) {\n\t\t\t\tif (weeks.disable) return\n\t\t\t\tif (this.cale.lastHover) return\n\t\t\t\tlet {\n\t\t\t\t\tbefore,\n\t\t\t\t\tafter\n\t\t\t\t} = this.cale.multipleStatus\n\t\t\t\tif (!before) return\n\t\t\t\tthis.calendar = weeks\n\t\t\t\t// 设置范围选\n\t\t\t\tthis.cale.setHoverMultiple(this.calendar.fullDate)\n\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t\t// hover时，进入一个日历，更新另一个\n\t\t\t\tif (this.firstEnter) {\n\t\t\t\t\tthis.$emit('firstEnterCale', this.cale.multipleStatus)\n\t\t\t\t\tthis.firstEnter = false\n\t\t\t\t}\n\t\t\t},\n\t\t\trangeWithinMonth(A, B) {\n\t\t\t\tconst [yearA, monthA] = A.split('-')\n\t\t\t\tconst [yearB, monthB] = B.split('-')\n\t\t\t\treturn yearA === yearB && monthA === monthB\n\t\t\t},\n\n\t\t\t// 取消穿透\n\t\t\tclean() {\n\t\t\t\tthis.close()\n\t\t\t},\n\n\t\t\t// 蒙版点击事件\n\t\t\tmaskClick() {\n\t\t\t\tthis.$emit('maskClose')\n\t\t\t},\n\n\t\t\tclearCalender() {\n\t\t\t\tif (this.range) {\n\t\t\t\t\tthis.timeRange.startTime = ''\n\t\t\t\t\tthis.timeRange.endTime = ''\n\t\t\t\t\tthis.tempRange.before = ''\n\t\t\t\t\tthis.tempRange.after = ''\n\t\t\t\t\tthis.cale.multipleStatus.before = ''\n\t\t\t\t\tthis.cale.multipleStatus.after = ''\n\t\t\t\t\tthis.cale.multipleStatus.data = []\n\t\t\t\t\tthis.cale.lastHover = false\n\t\t\t\t} else {\n\t\t\t\t\tthis.time = ''\n\t\t\t\t\tthis.tempSingleDate = ''\n\t\t\t\t}\n\t\t\t\tthis.calendar.fullDate = ''\n\t\t\t\tthis.setDate()\n\t\t\t},\n\n\t\t\tbindDateChange(e) {\n\t\t\t\tconst value = e.detail.value + '-1'\n\t\t\t\tthis.init(value)\n\t\t\t},\n\t\t\t/**\n\t\t\t * 初始化日期显示\n\t\t\t * @param {Object} date\n\t\t\t */\n\t\t\tinit(date) {\n\t\t\t\tthis.cale.setDate(date)\n\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t\tthis.nowDate = this.calendar = this.cale.getInfo(date)\n\t\t\t},\n\t\t\t// choiceDate(weeks) {\n\t\t\t// \tif (weeks.disable) return\n\t\t\t// \tthis.calendar = weeks\n\t\t\t// \t// 设置多选\n\t\t\t// \tthis.cale.setMultiple(this.calendar.fullDate, true)\n\t\t\t// \tthis.weeks = this.cale.weeks\n\t\t\t// \tthis.tempSingleDate = this.calendar.fullDate\n\t\t\t// \tthis.tempRange.before = this.cale.multipleStatus.before\n\t\t\t// \tthis.tempRange.after = this.cale.multipleStatus.after\n\t\t\t// \tthis.change()\n\t\t\t// },\n\t\t\t/**\n\t\t\t * 打开日历弹窗\n\t\t\t */\n\t\t\topen() {\n\t\t\t\t// 弹窗模式并且清理数据\n\t\t\t\tif (this.clearDate && !this.insert) {\n\t\t\t\t\tthis.cale.cleanMultipleStatus()\n\t\t\t\t\t// this.cale.setDate(this.date)\n\t\t\t\t\tthis.init(this.date)\n\t\t\t\t}\n\t\t\t\tthis.show = true\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tthis.aniMaskShow = true\n\t\t\t\t\t}, 50)\n\t\t\t\t})\n\t\t\t},\n\t\t\t/**\n\t\t\t * 关闭日历弹窗\n\t\t\t */\n\t\t\tclose() {\n\t\t\t\tthis.aniMaskShow = false\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tthis.show = false\n\t\t\t\t\t\tthis.$emit('close')\n\t\t\t\t\t}, 300)\n\t\t\t\t})\n\t\t\t},\n\t\t\t/**\n\t\t\t * 确认按钮\n\t\t\t */\n\t\t\tconfirm() {\n\t\t\t\tthis.setEmit('confirm')\n\t\t\t\tthis.close()\n\t\t\t},\n\t\t\t/**\n\t\t\t * 变化触发\n\t\t\t */\n\t\t\tchange() {\n\t\t\t\tif (!this.insert) return\n\t\t\t\tthis.setEmit('change')\n\t\t\t},\n\t\t\t/**\n\t\t\t * 选择月份触发\n\t\t\t */\n\t\t\tmonthSwitch() {\n\t\t\t\tlet {\n\t\t\t\t\tyear,\n\t\t\t\t\tmonth\n\t\t\t\t} = this.nowDate\n\t\t\t\tthis.$emit('monthSwitch', {\n\t\t\t\t\tyear,\n\t\t\t\t\tmonth: Number(month)\n\t\t\t\t})\n\t\t\t},\n\t\t\t/**\n\t\t\t * 派发事件\n\t\t\t * @param {Object} name\n\t\t\t */\n\t\t\tsetEmit(name) {\n\t\t\t\tlet {\n\t\t\t\t\tyear,\n\t\t\t\t\tmonth,\n\t\t\t\t\tdate,\n\t\t\t\t\tfullDate,\n\t\t\t\t\tlunar,\n\t\t\t\t\textraInfo\n\t\t\t\t} = this.calendar\n\t\t\t\tthis.$emit(name, {\n\t\t\t\t\trange: this.cale.multipleStatus,\n\t\t\t\t\tyear,\n\t\t\t\t\tmonth,\n\t\t\t\t\tdate,\n\t\t\t\t\ttime: this.time,\n\t\t\t\t\ttimeRange: this.timeRange,\n\t\t\t\t\tfulldate: fullDate,\n\t\t\t\t\tlunar,\n\t\t\t\t\textraInfo: extraInfo || {}\n\t\t\t\t})\n\t\t\t},\n\t\t\t/**\n\t\t\t * 选择天触发\n\t\t\t * @param {Object} weeks\n\t\t\t */\n\t\t\tchoiceDate(weeks) {\n\t\t\t\tif (weeks.disable) return\n\t\t\t\tthis.calendar = weeks\n\t\t\t\tthis.calendar.userChecked = true\n\t\t\t\t// 设置多选\n\t\t\t\tthis.cale.setMultiple(this.calendar.fullDate, true)\n\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t\tthis.tempSingleDate = this.calendar.fullDate\n\t\t\t\tconst beforeStatus = this.cale.multipleStatus.before\n\t\t\t\tconst beforeDate = new Date(this.cale.multipleStatus.before).getTime()\n\t\t\t\tconst afterDate = new Date(this.cale.multipleStatus.after).getTime()\n\t\t\t\tif (beforeDate > afterDate && afterDate) {\n\t\t\t\t\tthis.tempRange.before = this.cale.multipleStatus.after\n\t\t\t\t\tthis.tempRange.after = this.cale.multipleStatus.before\n\t\t\t\t} else {\n\t\t\t\t\tthis.tempRange.before = this.cale.multipleStatus.before\n\t\t\t\t\tthis.tempRange.after = this.cale.multipleStatus.after\n\t\t\t\t}\n\t\t\t\tthis.change()\n\t\t\t},\n\t\t\t/**\n\t\t\t * 回到今天\n\t\t\t */\n\t\t\tbacktoday() {\n\t\t\t\tlet date = this.cale.getDate(new Date()).fullDate\n\t\t\t\t// this.cale.setDate(date)\n\t\t\t\tthis.init(date)\n\t\t\t\tthis.change()\n\t\t\t},\n\t\t\t/**\n\t\t\t * 比较时间大小\n\t\t\t */\n\t\t\tdateCompare(startDate, endDate) {\n\t\t\t\t// 计算截止时间\n\t\t\t\tstartDate = new Date(startDate.replace('-', '/').replace('-', '/'))\n\t\t\t\t// 计算详细项的截止时间\n\t\t\t\tendDate = new Date(endDate.replace('-', '/').replace('-', '/'))\n\t\t\t\tif (startDate <= endDate) {\n\t\t\t\t\treturn true\n\t\t\t\t} else {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t},\n\t\t\t/**\n\t\t\t * 上个月\n\t\t\t */\n\t\t\tpre() {\n\t\t\t\tconst preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate\n\t\t\t\tthis.setDate(preDate)\n\t\t\t\tthis.monthSwitch()\n\n\t\t\t},\n\t\t\t/**\n\t\t\t * 下个月\n\t\t\t */\n\t\t\tnext() {\n\t\t\t\tconst nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate\n\t\t\t\tthis.setDate(nextDate)\n\t\t\t\tthis.monthSwitch()\n\t\t\t},\n\t\t\t/**\n\t\t\t * 设置日期\n\t\t\t * @param {Object} date\n\t\t\t */\n\t\t\tsetDate(date) {\n\t\t\t\tthis.cale.setDate(date)\n\t\t\t\tthis.weeks = this.cale.weeks\n\t\t\t\tthis.nowDate = this.cale.getInfo(date)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" >\n\t$uni-primary: #007aff !default;\n\n\t.uni-calendar {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: column;\n\t}\n\n\t.uni-calendar__mask {\n\t\tposition: fixed;\n\t\tbottom: 0;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbackground-color: rgba(0, 0, 0, 0.4);\n\t\ttransition-property: opacity;\n\t\ttransition-duration: 0.3s;\n\t\topacity: 0;\n\t\t/* #ifndef APP-NVUE */\n\t\tz-index: 99;\n\t\t/* #endif */\n\t}\n\n\t.uni-calendar--mask-show {\n\t\topacity: 1\n\t}\n\n\t.uni-calendar--fixed {\n\t\tposition: fixed;\n\t\tbottom: calc(var(--window-bottom));\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttransition-property: transform;\n\t\ttransition-duration: 0.3s;\n\t\ttransform: translateY(460px);\n\t\t/* #ifndef APP-NVUE */\n\t\tz-index: 99;\n\t\t/* #endif */\n\t}\n\n\t.uni-calendar--ani-show {\n\t\ttransform: translateY(0);\n\t}\n\n\t.uni-calendar__content {\n\t\tbackground-color: #fff;\n\t}\n\n\t.uni-calendar__content-mobile {\n\t\tborder-top-left-radius: 10px;\n\t\tborder-top-right-radius: 10px;\n\t\tbox-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.1);\n\t}\n\n\t.uni-calendar__header {\n\t\tposition: relative;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\theight: 50px;\n\t}\n\n\t.uni-calendar__header-mobile {\n\t\tpadding: 10px;\n\t\tpadding-bottom: 0;\n\t}\n\n\t.uni-calendar--fixed-top {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tjustify-content: space-between;\n\t\tborder-top-color: rgba(0, 0, 0, 0.4);\n\t\tborder-top-style: solid;\n\t\tborder-top-width: 1px;\n\t}\n\n\t.uni-calendar--fixed-width {\n\t\twidth: 50px;\n\t}\n\n\t.uni-calendar__backtoday {\n\t\tposition: absolute;\n\t\tright: 0;\n\t\ttop: 25rpx;\n\t\tpadding: 0 5px;\n\t\tpadding-left: 10px;\n\t\theight: 25px;\n\t\tline-height: 25px;\n\t\tfont-size: 12px;\n\t\tborder-top-left-radius: 25px;\n\t\tborder-bottom-left-radius: 25px;\n\t\tcolor: #fff;\n\t\tbackground-color: #f1f1f1;\n\t}\n\n\t.uni-calendar__header-text {\n\t\ttext-align: center;\n\t\twidth: 100px;\n\t\tfont-size: 15px;\n\t\tcolor: #666;\n\t}\n\n\t.uni-calendar__button-text {\n\t\ttext-align: center;\n\t\twidth: 100px;\n\t\tfont-size: 14px;\n\t\tcolor: $uni-primary;\n\t\t/* #ifndef APP-NVUE */\n\t\tletter-spacing: 3px;\n\t\t/* #endif */\n\t}\n\n\t.uni-calendar__header-btn-box {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\twidth: 50px;\n\t\theight: 50px;\n\t}\n\n\t.uni-calendar__header-btn {\n\t\twidth: 9px;\n\t\theight: 9px;\n\t\tborder-left-color: #808080;\n\t\tborder-left-style: solid;\n\t\tborder-left-width: 1px;\n\t\tborder-top-color: #555555;\n\t\tborder-top-style: solid;\n\t\tborder-top-width: 1px;\n\t}\n\n\t.uni-calendar--left {\n\t\ttransform: rotate(-45deg);\n\t}\n\n\t.uni-calendar--right {\n\t\ttransform: rotate(135deg);\n\t}\n\n\n\t.uni-calendar__weeks {\n\t\tposition: relative;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t}\n\n\t.uni-calendar__weeks-item {\n\t\tflex: 1;\n\t}\n\n\t.uni-calendar__weeks-day {\n\t\tflex: 1;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\theight: 40px;\n\t\tborder-bottom-color: #F5F5F5;\n\t\tborder-bottom-style: solid;\n\t\tborder-bottom-width: 1px;\n\t}\n\n\t.uni-calendar__weeks-day-text {\n\t\tfont-size: 12px;\n\t\tcolor: #B2B2B2;\n\t}\n\n\t.uni-calendar__box {\n\t\tposition: relative;\n\t\t// padding: 0 10px;\n\t\tpadding-bottom: 7px;\n\t}\n\n\t.uni-calendar__box-bg {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t}\n\n\t.uni-calendar__box-bg-text {\n\t\tfont-size: 200px;\n\t\tfont-weight: bold;\n\t\tcolor: #999;\n\t\topacity: 0.1;\n\t\ttext-align: center;\n\t\t/* #ifndef APP-NVUE */\n\t\tline-height: 1;\n\t\t/* #endif */\n\t}\n\n\t.uni-date-changed {\n\t\tpadding: 0 10px;\n\t\t// line-height: 50px;\n\t\ttext-align: center;\n\t\tcolor: #333;\n\t\tborder-top-color: #DCDCDC;\n\t\t;\n\t\tborder-top-style: solid;\n\t\tborder-top-width: 1px;\n\t\tflex: 1;\n\t}\n\n\t.uni-date-btn--ok {\n\t\tpadding: 20px 15px;\n\t}\n\n\t.uni-date-changed--time-start {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\talign-items: center;\n\t}\n\n\t.uni-date-changed--time-end {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\talign-items: center;\n\t}\n\n\t.uni-date-changed--time-date {\n\t\tcolor: #999;\n\t\tline-height: 50px;\n\t\tmargin-right: 5px;\n\t\t// opacity: 0.6;\n\t}\n\n\t.time-picker-style {\n\t\t// width: 62px;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tjustify-content: center;\n\t\talign-items: center\n\t}\n\n\t.mr-10 {\n\t\tmargin-right: 10px;\n\t}\n\n\t.dialog-close {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tpadding: 0 25px;\n\t\tmargin-top: 10px;\n\t}\n\n\t.dialog-close-plus {\n\t\twidth: 16px;\n\t\theight: 2px;\n\t\tbackground-color: #737987;\n\t\tborder-radius: 2px;\n\t\ttransform: rotate(45deg);\n\t}\n\n\t.dialog-close-rotate {\n\t\tposition: absolute;\n\t\ttransform: rotate(-45deg);\n\t}\n\n\t.uni-datetime-picker--btn {\n\t\tborder-radius: 100px;\n\t\theight: 40px;\n\t\tline-height: 40px;\n\t\tbackground-color: $uni-primary;\n\t\tcolor: #fff;\n\t\tfont-size: 16px;\n\t\tletter-spacing: 2px;\n\t}\n\n\t/* #ifndef APP-NVUE */\n\t.uni-datetime-picker--btn:active {\n\t\topacity: 0.7;\n\t}\n\t/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json",
    "content": "{\n\t\"uni-datetime-picker.selectDate\": \"select date\",\n\t\"uni-datetime-picker.selectTime\": \"select time\",\n\t\"uni-datetime-picker.selectDateTime\": \"select datetime\",\n\t\"uni-datetime-picker.startDate\": \"start date\",\n\t\"uni-datetime-picker.endDate\": \"end date\",\n\t\"uni-datetime-picker.startTime\": \"start time\",\n\t\"uni-datetime-picker.endTime\": \"end time\",\n\t\"uni-datetime-picker.ok\": \"ok\",\n\t\"uni-datetime-picker.clear\": \"clear\",\n\t\"uni-datetime-picker.cancel\": \"cancel\",\n\t\"uni-datetime-picker.year\": \"-\",\n\t\"uni-datetime-picker.month\": \"\",\n\t\"uni-calender.MON\": \"MON\",\n\t\"uni-calender.TUE\": \"TUE\",\n\t\"uni-calender.WED\": \"WED\",\n\t\"uni-calender.THU\": \"THU\",\n\t\"uni-calender.FRI\": \"FRI\",\n\t\"uni-calender.SAT\": \"SAT\",\n\t\"uni-calender.SUN\": \"SUN\",\n\t\"uni-calender.confirm\": \"confirm\"\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js",
    "content": "import en from './en.json'\nimport zhHans from './zh-Hans.json'\nimport zhHant from './zh-Hant.json'\nexport default {\n\ten,\n\t'zh-Hans': zhHans,\n\t'zh-Hant': zhHant\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json",
    "content": "{\n\t\"uni-datetime-picker.selectDate\": \"选择日期\",\n\t\"uni-datetime-picker.selectTime\": \"选择时间\",\n\t\"uni-datetime-picker.selectDateTime\": \"选择日期时间\",\n\t\"uni-datetime-picker.startDate\": \"开始日期\",\n\t\"uni-datetime-picker.endDate\": \"结束日期\",\n\t\"uni-datetime-picker.startTime\": \"开始时间\",\n\t\"uni-datetime-picker.endTime\": \"结束时间\",\n\t\"uni-datetime-picker.ok\": \"确定\",\n\t\"uni-datetime-picker.clear\": \"清除\",\n\t\"uni-datetime-picker.cancel\": \"取消\",\n\t\"uni-datetime-picker.year\": \"年\",\n\t\"uni-datetime-picker.month\": \"月\",\n\t\"uni-calender.SUN\": \"日\",\n\t\"uni-calender.MON\": \"一\",\n\t\"uni-calender.TUE\": \"二\",\n\t\"uni-calender.WED\": \"三\",\n\t\"uni-calender.THU\": \"四\",\n\t\"uni-calender.FRI\": \"五\",\n\t\"uni-calender.SAT\": \"六\",\n\t\"uni-calender.confirm\": \"确认\"\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json",
    "content": "{\n  \"uni-datetime-picker.selectDate\": \"選擇日期\",\n  \"uni-datetime-picker.selectTime\": \"選擇時間\",\n  \"uni-datetime-picker.selectDateTime\": \"選擇日期時間\",\n  \"uni-datetime-picker.startDate\": \"開始日期\",\n  \"uni-datetime-picker.endDate\": \"結束日期\",\n  \"uni-datetime-picker.startTime\": \"開始时间\",\n  \"uni-datetime-picker.endTime\": \"結束时间\",\n  \"uni-datetime-picker.ok\": \"確定\",\n  \"uni-datetime-picker.clear\": \"清除\",\n  \"uni-datetime-picker.cancel\": \"取消\",\n  \"uni-datetime-picker.year\": \"年\",\n  \"uni-datetime-picker.month\": \"月\",\n  \"uni-calender.SUN\": \"日\",\n  \"uni-calender.MON\": \"一\",\n  \"uni-calender.TUE\": \"二\",\n  \"uni-calender.WED\": \"三\",\n  \"uni-calender.THU\": \"四\",\n  \"uni-calender.FRI\": \"五\",\n  \"uni-calender.SAT\": \"六\",\n  \"uni-calender.confirm\": \"確認\"\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/keypress.js",
    "content": "// #ifdef H5\nexport default {\n  name: 'Keypress',\n  props: {\n    disable: {\n      type: Boolean,\n      default: false\n    }\n  },\n  mounted () {\n    const keyNames = {\n      esc: ['Esc', 'Escape'],\n      tab: 'Tab',\n      enter: 'Enter',\n      space: [' ', 'Spacebar'],\n      up: ['Up', 'ArrowUp'],\n      left: ['Left', 'ArrowLeft'],\n      right: ['Right', 'ArrowRight'],\n      down: ['Down', 'ArrowDown'],\n      delete: ['Backspace', 'Delete', 'Del']\n    }\n    const listener = ($event) => {\n      if (this.disable) {\n        return\n      }\n      const keyName = Object.keys(keyNames).find(key => {\n        const keyName = $event.key\n        const value = keyNames[key]\n        return value === keyName || (Array.isArray(value) && value.includes(keyName))\n      })\n      if (keyName) {\n        // 避免和其他按键事件冲突\n        setTimeout(() => {\n          this.$emit(keyName, {})\n        }, 0)\n      }\n    }\n    document.addEventListener('keyup', listener)\n    this.$once('hook:beforeDestroy', () => {\n      document.removeEventListener('keyup', listener)\n    })\n  },\n\trender: () => {}\n}\n// #endif"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue",
    "content": "<template>\n\t<view class=\"uni-datetime-picker\">\n\t\t<view @click=\"initTimePicker\">\n\t\t\t<slot>\n\t\t\t\t<view class=\"uni-datetime-picker-timebox-pointer\"\n\t\t\t\t\t:class=\"{'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border}\">\n\t\t\t\t\t<text class=\"uni-datetime-picker-text\">{{time}}</text>\n\t\t\t\t\t<view v-if=\"!time\" class=\"uni-datetime-picker-time\">\n\t\t\t\t\t\t<text class=\"uni-datetime-picker-text\">{{selectTimeText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</slot>\n\t\t</view>\n\t\t<view v-if=\"visible\" id=\"mask\" class=\"uni-datetime-picker-mask\" @click=\"tiggerTimePicker\"></view>\n\t\t<view v-if=\"visible\" class=\"uni-datetime-picker-popup\" :class=\"[dateShow && timeShow ? '' : 'fix-nvue-height']\"\n\t\t\t:style=\"fixNvueBug\">\n\t\t\t<view class=\"uni-title\">\n\t\t\t\t<text class=\"uni-datetime-picker-text\">{{selectTimeText}}</text>\n\t\t\t</view>\n\t\t\t<view v-if=\"dateShow\" class=\"uni-datetime-picker__container-box\">\n\t\t\t\t<picker-view class=\"uni-datetime-picker-view\" :indicator-style=\"indicatorStyle\" :value=\"ymd\"\n\t\t\t\t\t@change=\"bindDateChange\">\n\t\t\t\t\t<picker-view-column>\n\t\t\t\t\t\t<view class=\"uni-datetime-picker-item\" v-for=\"(item,index) in years\" :key=\"index\">\n\t\t\t\t\t\t\t<text class=\"uni-datetime-picker-item\">{{lessThanTen(item)}}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</picker-view-column>\n\t\t\t\t\t<picker-view-column>\n\t\t\t\t\t\t<view class=\"uni-datetime-picker-item\" v-for=\"(item,index) in months\" :key=\"index\">\n\t\t\t\t\t\t\t<text class=\"uni-datetime-picker-item\">{{lessThanTen(item)}}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</picker-view-column>\n\t\t\t\t\t<picker-view-column>\n\t\t\t\t\t\t<view class=\"uni-datetime-picker-item\" v-for=\"(item,index) in days\" :key=\"index\">\n\t\t\t\t\t\t\t<text class=\"uni-datetime-picker-item\">{{lessThanTen(item)}}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</picker-view-column>\n\t\t\t\t</picker-view>\n\t\t\t\t<!-- 兼容 nvue 不支持伪类 -->\n\t\t\t\t<text class=\"uni-datetime-picker-sign sign-left\">-</text>\n\t\t\t\t<text class=\"uni-datetime-picker-sign sign-right\">-</text>\n\t\t\t</view>\n\t\t\t<view v-if=\"timeShow\" class=\"uni-datetime-picker__container-box\">\n\t\t\t\t<picker-view class=\"uni-datetime-picker-view\" :class=\"[hideSecond ? 'time-hide-second' : '']\"\n\t\t\t\t\t:indicator-style=\"indicatorStyle\" :value=\"hms\" @change=\"bindTimeChange\">\n\t\t\t\t\t<picker-view-column>\n\t\t\t\t\t\t<view class=\"uni-datetime-picker-item\" v-for=\"(item,index) in hours\" :key=\"index\">\n\t\t\t\t\t\t\t<text class=\"uni-datetime-picker-item\">{{lessThanTen(item)}}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</picker-view-column>\n\t\t\t\t\t<picker-view-column>\n\t\t\t\t\t\t<view class=\"uni-datetime-picker-item\" v-for=\"(item,index) in minutes\" :key=\"index\">\n\t\t\t\t\t\t\t<text class=\"uni-datetime-picker-item\">{{lessThanTen(item)}}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</picker-view-column>\n\t\t\t\t\t<picker-view-column v-if=\"!hideSecond\">\n\t\t\t\t\t\t<view class=\"uni-datetime-picker-item\" v-for=\"(item,index) in seconds\" :key=\"index\">\n\t\t\t\t\t\t\t<text class=\"uni-datetime-picker-item\">{{lessThanTen(item)}}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</picker-view-column>\n\t\t\t\t</picker-view>\n\t\t\t\t<!-- 兼容 nvue 不支持伪类 -->\n\t\t\t\t<text class=\"uni-datetime-picker-sign\" :class=\"[hideSecond ? 'sign-center' : 'sign-left']\">:</text>\n\t\t\t\t<text v-if=\"!hideSecond\" class=\"uni-datetime-picker-sign sign-right\">:</text>\n\t\t\t</view>\n\t\t\t<view class=\"uni-datetime-picker-btn\">\n\t\t\t\t<view @click=\"clearTime\">\n\t\t\t\t\t<text class=\"uni-datetime-picker-btn-text\">{{clearText}}</text>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"uni-datetime-picker-btn-group\">\n\t\t\t\t\t<view class=\"uni-datetime-picker-cancel\" @click=\"tiggerTimePicker\">\n\t\t\t\t\t\t<text class=\"uni-datetime-picker-btn-text\">{{cancelText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view @click=\"setTime\">\n\t\t\t\t\t\t<text class=\"uni-datetime-picker-btn-text\">{{okText}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t\t<!-- #ifdef H5 -->\n\t\t<!-- <keypress v-if=\"visible\" @esc=\"tiggerTimePicker\" @enter=\"setTime\" /> -->\n\t\t<!-- #endif -->\n\t</view>\n</template>\n\n<script>\n\t// #ifdef H5\n\timport keypress from './keypress'\n\t// #endif\n\timport {\n\t\tinitVueI18n\n\t} from '@dcloudio/uni-i18n'\n\timport messages from './i18n/index.js'\n\tconst {\tt\t} = initVueI18n(messages)\n\n\t/**\n\t * DatetimePicker 时间选择器\n\t * @description 可以同时选择日期和时间的选择器\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=xxx\n\t * @property {String} type = [datetime | date | time] 显示模式\n\t * @property {Boolean} multiple = [true|false] 是否多选\n\t * @property {String|Number} value 默认值\n\t * @property {String|Number} start 起始日期或时间\n\t * @property {String|Number} end 起始日期或时间\n\t * @property {String} return-type = [timestamp | string]\n\t * @event {Function} change  选中发生变化触发\n\t */\n\n\texport default {\n\t\tname: 'UniDatetimePicker',\n\t\tcomponents: {\n\t\t\t// #ifdef H5\n\t\t\tkeypress\n\t\t\t// #endif\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tindicatorStyle: `height: 50px;`,\n\t\t\t\tvisible: false,\n\t\t\t\tfixNvueBug: {},\n\t\t\t\tdateShow: true,\n\t\t\t\ttimeShow: true,\n\t\t\t\ttitle: '日期和时间',\n\t\t\t\t// 输入框当前时间\n\t\t\t\ttime: '',\n\t\t\t\t// 当前的年月日时分秒\n\t\t\t\tyear: 1920,\n\t\t\t\tmonth: 0,\n\t\t\t\tday: 0,\n\t\t\t\thour: 0,\n\t\t\t\tminute: 0,\n\t\t\t\tsecond: 0,\n\t\t\t\t// 起始时间\n\t\t\t\tstartYear: 1920,\n\t\t\t\tstartMonth: 1,\n\t\t\t\tstartDay: 1,\n\t\t\t\tstartHour: 0,\n\t\t\t\tstartMinute: 0,\n\t\t\t\tstartSecond: 0,\n\t\t\t\t// 结束时间\n\t\t\t\tendYear: 2120,\n\t\t\t\tendMonth: 12,\n\t\t\t\tendDay: 31,\n\t\t\t\tendHour: 23,\n\t\t\t\tendMinute: 59,\n\t\t\t\tendSecond: 59,\n\t\t\t}\n\t\t},\n\t\tprops: {\n\t\t\ttype: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'datetime'\n\t\t\t},\n\t\t\tvalue: {\n\t\t\t\ttype: [String, Number],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tmodelValue: {\n\t\t\t\ttype: [String, Number],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tstart: {\n\t\t\t\ttype: [Number, String],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tend: {\n\t\t\t\ttype: [Number, String],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\treturnType: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'string'\n\t\t\t},\n\t\t\tdisabled: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tborder: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\thideSecond: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tvalue: {\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tif (newVal) {\n\t\t\t\t\t\tthis.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式\n\t\t\t\t\t\tthis.initTime(false)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.time = ''\n\t\t\t\t\t\tthis.parseValue(Date.now())\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\timmediate: true\n\t\t\t},\n\t\t\ttype: {\n\t\t\t\thandler(newValue) {\n\t\t\t\t\tif (newValue === 'date') {\n\t\t\t\t\t\tthis.dateShow = true\n\t\t\t\t\t\tthis.timeShow = false\n\t\t\t\t\t\tthis.title = '日期'\n\t\t\t\t\t} else if (newValue === 'time') {\n\t\t\t\t\t\tthis.dateShow = false\n\t\t\t\t\t\tthis.timeShow = true\n\t\t\t\t\t\tthis.title = '时间'\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.dateShow = true\n\t\t\t\t\t\tthis.timeShow = true\n\t\t\t\t\t\tthis.title = '日期和时间'\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\timmediate: true\n\t\t\t},\n\t\t\tstart: {\n\t\t\t\thandler(newVal) {\n\t\t\t\t\tthis.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') //兼容 iOS、safari 日期格式\n\t\t\t\t},\n\t\t\t\timmediate: true\n\t\t\t},\n\t\t\tend: {\n\t\t\t\thandler(newVal) {\n\t\t\t\t\tthis.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') //兼容 iOS、safari 日期格式\n\t\t\t\t},\n\t\t\t\timmediate: true\n\t\t\t},\n\n\t\t\t// 月、日、时、分、秒可选范围变化后，检查当前值是否在范围内，不在则当前值重置为可选范围第一项\n\t\t\tmonths(newVal) {\n\t\t\t\tthis.checkValue('month', this.month, newVal)\n\t\t\t},\n\t\t\tdays(newVal) {\n\t\t\t\tthis.checkValue('day', this.day, newVal)\n\t\t\t},\n\t\t\thours(newVal) {\n\t\t\t\tthis.checkValue('hour', this.hour, newVal)\n\t\t\t},\n\t\t\tminutes(newVal) {\n\t\t\t\tthis.checkValue('minute', this.minute, newVal)\n\t\t\t},\n\t\t\tseconds(newVal) {\n\t\t\t\tthis.checkValue('second', this.second, newVal)\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 当前年、月、日、时、分、秒选择范围\n\t\t\tyears() {\n\t\t\t\treturn this.getCurrentRange('year')\n\t\t\t},\n\n\t\t\tmonths() {\n\t\t\t\treturn this.getCurrentRange('month')\n\t\t\t},\n\n\t\t\tdays() {\n\t\t\t\treturn this.getCurrentRange('day')\n\t\t\t},\n\n\t\t\thours() {\n\t\t\t\treturn this.getCurrentRange('hour')\n\t\t\t},\n\n\t\t\tminutes() {\n\t\t\t\treturn this.getCurrentRange('minute')\n\t\t\t},\n\n\t\t\tseconds() {\n\t\t\t\treturn this.getCurrentRange('second')\n\t\t\t},\n\n\t\t\t// picker 当前值数组\n\t\t\tymd() {\n\t\t\t\treturn [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay]\n\t\t\t},\n\t\t\thms() {\n\t\t\t\treturn [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond]\n\t\t\t},\n\n\t\t\t// 当前 date 是 start\n\t\t\tcurrentDateIsStart() {\n\t\t\t\treturn this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay\n\t\t\t},\n\n\t\t\t// 当前 date 是 end\n\t\t\tcurrentDateIsEnd() {\n\t\t\t\treturn this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay\n\t\t\t},\n\n\t\t\t// 当前年、月、日、时、分、秒的最小值和最大值\n\t\t\tminYear() {\n\t\t\t\treturn this.startYear\n\t\t\t},\n\t\t\tmaxYear() {\n\t\t\t\treturn this.endYear\n\t\t\t},\n\t\t\tminMonth() {\n\t\t\t\tif (this.year === this.startYear) {\n\t\t\t\t\treturn this.startMonth\n\t\t\t\t} else {\n\t\t\t\t\treturn 1\n\t\t\t\t}\n\t\t\t},\n\t\t\tmaxMonth() {\n\t\t\t\tif (this.year === this.endYear) {\n\t\t\t\t\treturn this.endMonth\n\t\t\t\t} else {\n\t\t\t\t\treturn 12\n\t\t\t\t}\n\t\t\t},\n\t\t\tminDay() {\n\t\t\t\tif (this.year === this.startYear && this.month === this.startMonth) {\n\t\t\t\t\treturn this.startDay\n\t\t\t\t} else {\n\t\t\t\t\treturn 1\n\t\t\t\t}\n\t\t\t},\n\t\t\tmaxDay() {\n\t\t\t\tif (this.year === this.endYear && this.month === this.endMonth) {\n\t\t\t\t\treturn this.endDay\n\t\t\t\t} else {\n\t\t\t\t\treturn this.daysInMonth(this.year, this.month)\n\t\t\t\t}\n\t\t\t},\n\t\t\tminHour() {\n\t\t\t\tif (this.type === 'datetime') {\n\t\t\t\t\tif (this.currentDateIsStart) {\n\t\t\t\t\t\treturn this.startHour\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.type === 'time') {\n\t\t\t\t\treturn this.startHour\n\t\t\t\t}\n\t\t\t},\n\t\t\tmaxHour() {\n\t\t\t\tif (this.type === 'datetime') {\n\t\t\t\t\tif (this.currentDateIsEnd) {\n\t\t\t\t\t\treturn this.endHour\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 23\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.type === 'time') {\n\t\t\t\t\treturn this.endHour\n\t\t\t\t}\n\t\t\t},\n\t\t\tminMinute() {\n\t\t\t\tif (this.type === 'datetime') {\n\t\t\t\t\tif (this.currentDateIsStart && this.hour === this.startHour) {\n\t\t\t\t\t\treturn this.startMinute\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.type === 'time') {\n\t\t\t\t\tif (this.hour === this.startHour) {\n\t\t\t\t\t\treturn this.startMinute\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tmaxMinute() {\n\t\t\t\tif (this.type === 'datetime') {\n\t\t\t\t\tif (this.currentDateIsEnd && this.hour === this.endHour) {\n\t\t\t\t\t\treturn this.endMinute\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 59\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.type === 'time') {\n\t\t\t\t\tif (this.hour === this.endHour) {\n\t\t\t\t\t\treturn this.endMinute\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 59\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tminSecond() {\n\t\t\t\tif (this.type === 'datetime') {\n\t\t\t\t\tif (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) {\n\t\t\t\t\t\treturn this.startSecond\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.type === 'time') {\n\t\t\t\t\tif (this.hour === this.startHour && this.minute === this.startMinute) {\n\t\t\t\t\t\treturn this.startSecond\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 0\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tmaxSecond() {\n\t\t\t\tif (this.type === 'datetime') {\n\t\t\t\t\tif (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) {\n\t\t\t\t\t\treturn this.endSecond\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 59\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.type === 'time') {\n\t\t\t\t\tif (this.hour === this.endHour && this.minute === this.endMinute) {\n\t\t\t\t\t\treturn this.endSecond\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 59\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * for i18n\n\t\t\t */\n\t\t\tselectTimeText() {\n\t\t\t\treturn t(\"uni-datetime-picker.selectTime\")\n\t\t\t},\n\t\t\tokText() {\n\t\t\t\treturn t(\"uni-datetime-picker.ok\")\n\t\t\t},\n\t\t\tclearText() {\n\t\t\t\treturn t(\"uni-datetime-picker.clear\")\n\t\t\t},\n\t\t\tcancelText() {\n\t\t\t\treturn t(\"uni-datetime-picker.cancel\")\n\t\t\t}\n\t\t},\n\n\t\tmounted() {\n\t\t\t// #ifdef APP-NVUE\n\t\t\tconst res = uni.getSystemInfoSync();\n\t\t\tthis.fixNvueBug = {\n\t\t\t\ttop: res.windowHeight / 2,\n\t\t\t\tleft: res.windowWidth / 2\n\t\t\t}\n\t\t\t// #endif\n\t\t},\n\n\t\tmethods: {\n\t\t\t/**\n\t\t\t * @param {Object} item\n\t\t\t * 小于 10 在前面加个 0\n\t\t\t */\n\n\t\t\tlessThanTen(item) {\n\t\t\t\treturn item < 10 ? '0' + item : item\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 解析时分秒字符串，例如：00:00:00\n\t\t\t * @param {String} timeString\n\t\t\t */\n\t\t\tparseTimeType(timeString) {\n\t\t\t\tif (timeString) {\n\t\t\t\t\tlet timeArr = timeString.split(':')\n\t\t\t\t\tthis.hour = Number(timeArr[0])\n\t\t\t\t\tthis.minute = Number(timeArr[1])\n\t\t\t\t\tthis.second = Number(timeArr[2])\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 解析选择器初始值，类型可以是字符串、时间戳，例如：2000-10-02、'08:30:00'、 1610695109000\n\t\t\t * @param {String | Number} datetime\n\t\t\t */\n\t\t\tinitPickerValue(datetime) {\n\t\t\t\tlet defaultValue = null\n\t\t\t\tif (datetime) {\n\t\t\t\t\tdefaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end)\n\t\t\t\t} else {\n\t\t\t\t\tdefaultValue = Date.now()\n\t\t\t\t\tdefaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end)\n\t\t\t\t}\n\t\t\t\tthis.parseValue(defaultValue)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 初始值规则：\n\t\t\t * - 用户设置初始值 value\n\t\t\t * \t- 设置了起始时间 start、终止时间 end，并 start < value < end，初始值为 value， 否则初始值为 start\n\t\t\t * \t- 只设置了起始时间 start，并 start < value，初始值为 value，否则初始值为 start\n\t\t\t * \t- 只设置了终止时间 end，并 value < end，初始值为 value，否则初始值为 end\n\t\t\t * \t- 无起始终止时间，则初始值为 value\n\t\t\t * - 无初始值 value，则初始值为当前本地时间 Date.now()\n\t\t\t * @param {Object} value\n\t\t\t * @param {Object} dateBase\n\t\t\t */\n\t\t\tcompareValueWithStartAndEnd(value, start, end) {\n\t\t\t\tlet winner = null\n\t\t\t\tvalue = this.superTimeStamp(value)\n\t\t\t\tstart = this.superTimeStamp(start)\n\t\t\t\tend = this.superTimeStamp(end)\n\n\t\t\t\tif (start && end) {\n\t\t\t\t\tif (value < start) {\n\t\t\t\t\t\twinner = new Date(start)\n\t\t\t\t\t} else if (value > end) {\n\t\t\t\t\t\twinner = new Date(end)\n\t\t\t\t\t} else {\n\t\t\t\t\t\twinner = new Date(value)\n\t\t\t\t\t}\n\t\t\t\t} else if (start && !end) {\n\t\t\t\t\twinner = start <= value ? new Date(value) : new Date(start)\n\t\t\t\t} else if (!start && end) {\n\t\t\t\t\twinner = value <= end ? new Date(value) : new Date(end)\n\t\t\t\t} else {\n\t\t\t\t\twinner = new Date(value)\n\t\t\t\t}\n\n\t\t\t\treturn winner\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 转换为可比较的时间戳，接受日期、时分秒、时间戳\n\t\t\t * @param {Object} value\n\t\t\t */\n\t\t\tsuperTimeStamp(value) {\n\t\t\t\tlet dateBase = ''\n\t\t\t\tif (this.type === 'time' && value && typeof value === 'string') {\n\t\t\t\t\tconst now = new Date()\n\t\t\t\t\tconst year = now.getFullYear()\n\t\t\t\t\tconst month = now.getMonth() + 1\n\t\t\t\t\tconst day = now.getDate()\n\t\t\t\t\tdateBase = year + '/' + month + '/' + day + ' '\n\t\t\t\t}\n\t\t\t\tif (Number(value) && typeof value !== NaN) {\n\t\t\t\t\tvalue = parseInt(value)\n\t\t\t\t\tdateBase = 0\n\t\t\t\t}\n\t\t\t\treturn this.createTimeStamp(dateBase + value)\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 解析默认值 value，字符串、时间戳\n\t\t\t * @param {Object} defaultTime\n\t\t\t */\n\t\t\tparseValue(value) {\n\t\t\t\tif (!value) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif (this.type === 'time' && typeof value === \"string\") {\n\t\t\t\t\tthis.parseTimeType(value)\n\t\t\t\t} else {\n\t\t\t\t\tlet defaultDate = null\n\t\t\t\t\tdefaultDate = new Date(value)\n\t\t\t\t\tif (this.type !== 'time') {\n\t\t\t\t\t\tthis.year = defaultDate.getFullYear()\n\t\t\t\t\t\tthis.month = defaultDate.getMonth() + 1\n\t\t\t\t\t\tthis.day = defaultDate.getDate()\n\t\t\t\t\t}\n\t\t\t\t\tif (this.type !== 'date') {\n\t\t\t\t\t\tthis.hour = defaultDate.getHours()\n\t\t\t\t\t\tthis.minute = defaultDate.getMinutes()\n\t\t\t\t\t\tthis.second = defaultDate.getSeconds()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (this.hideSecond) {\n\t\t\t\t\tthis.second = 0\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 解析可选择时间范围 start、end，年月日字符串、时间戳\n\t\t\t * @param {Object} defaultTime\n\t\t\t */\n\t\t\tparseDatetimeRange(point, pointType) {\n\t\t\t\t// 时间为空，则重置为初始值\n\t\t\t\tif (!point) {\n\t\t\t\t\tif (pointType === 'start') {\n\t\t\t\t\t\tthis.startYear = 1920\n\t\t\t\t\t\tthis.startMonth = 1\n\t\t\t\t\t\tthis.startDay = 1\n\t\t\t\t\t\tthis.startHour = 0\n\t\t\t\t\t\tthis.startMinute = 0\n\t\t\t\t\t\tthis.startSecond = 0\n\t\t\t\t\t}\n\t\t\t\t\tif (pointType === 'end') {\n\t\t\t\t\t\tthis.endYear = 2120\n\t\t\t\t\t\tthis.endMonth = 12\n\t\t\t\t\t\tthis.endDay = 31\n\t\t\t\t\t\tthis.endHour = 23\n\t\t\t\t\t\tthis.endMinute = 59\n\t\t\t\t\t\tthis.endSecond = 59\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif (this.type === 'time') {\n\t\t\t\t\tconst pointArr = point.split(':')\n\t\t\t\t\tthis[pointType + 'Hour'] = Number(pointArr[0])\n\t\t\t\t\tthis[pointType + 'Minute'] = Number(pointArr[1])\n\t\t\t\t\tthis[pointType + 'Second'] = Number(pointArr[2])\n\t\t\t\t} else {\n\t\t\t\t\tif (!point) {\n\t\t\t\t\t\tpointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tif (Number(point) && Number(point) !== NaN) {\n\t\t\t\t\t\tpoint = parseInt(point)\n\t\t\t\t\t}\n\t\t\t\t\t// datetime 的 end 没有时分秒, 则不限制\n\t\t\t\t\tconst hasTime = /[0-9]:[0-9]/\n\t\t\t\t\tif (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test(\n\t\t\t\t\t\t\tpoint)) {\n\t\t\t\t\t\tpoint = point + ' 23:59:59'\n\t\t\t\t\t}\n\t\t\t\t\tconst pointDate = new Date(point)\n\t\t\t\t\tthis[pointType + 'Year'] = pointDate.getFullYear()\n\t\t\t\t\tthis[pointType + 'Month'] = pointDate.getMonth() + 1\n\t\t\t\t\tthis[pointType + 'Day'] = pointDate.getDate()\n\t\t\t\t\tif (this.type === 'datetime') {\n\t\t\t\t\t\tthis[pointType + 'Hour'] = pointDate.getHours()\n\t\t\t\t\t\tthis[pointType + 'Minute'] = pointDate.getMinutes()\n\t\t\t\t\t\tthis[pointType + 'Second'] = pointDate.getSeconds()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// 获取 年、月、日、时、分、秒 当前可选范围\n\t\t\tgetCurrentRange(value) {\n\t\t\t\tconst range = []\n\t\t\t\tfor (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) {\n\t\t\t\t\trange.push(i)\n\t\t\t\t}\n\t\t\t\treturn range\n\t\t\t},\n\n\t\t\t// 字符串首字母大写\n\t\t\tcapitalize(str) {\n\t\t\t\treturn str.charAt(0).toUpperCase() + str.slice(1)\n\t\t\t},\n\n\t\t\t// 检查当前值是否在范围内，不在则当前值重置为可选范围第一项\n\t\t\tcheckValue(name, value, values) {\n\t\t\t\tif (values.indexOf(value) === -1) {\n\t\t\t\t\tthis[name] = values[0]\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// 每个月的实际天数\n\t\t\tdaysInMonth(year, month) { // Use 1 for January, 2 for February, etc.\n\t\t\t\treturn new Date(year, month, 0).getDate();\n\t\t\t},\n\n\t\t\t//兼容 iOS、safari 日期格式\n\t\t\tfixIosDateFormat(value) {\n\t\t\t\tif (typeof value === 'string') {\n\t\t\t\t\tvalue = value.replace(/-/g, '/')\n\t\t\t\t}\n\t\t\t\treturn value\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 生成时间戳\n\t\t\t * @param {Object} time\n\t\t\t */\n\t\t\tcreateTimeStamp(time) {\n\t\t\t\tif (!time) return\n\t\t\t\tif (typeof time === \"number\") {\n\t\t\t\t\treturn time\n\t\t\t\t} else {\n\t\t\t\t\ttime = time.replace(/-/g, '/')\n\t\t\t\t\tif (this.type === 'date') {\n\t\t\t\t\t\ttime = time + ' ' + '00:00:00'\n\t\t\t\t\t}\n\t\t\t\t\treturn Date.parse(time)\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 生成日期或时间的字符串\n\t\t\t */\n\t\t\tcreateDomSting() {\n\t\t\t\tconst yymmdd = this.year +\n\t\t\t\t\t'-' +\n\t\t\t\t\tthis.lessThanTen(this.month) +\n\t\t\t\t\t'-' +\n\t\t\t\t\tthis.lessThanTen(this.day)\n\n\t\t\t\tlet hhmmss = this.lessThanTen(this.hour) +\n\t\t\t\t\t':' +\n\t\t\t\t\tthis.lessThanTen(this.minute)\n\n\t\t\t\tif (!this.hideSecond) {\n\t\t\t\t\thhmmss = hhmmss + ':' + this.lessThanTen(this.second)\n\t\t\t\t}\n\n\t\t\t\tif (this.type === 'date') {\n\t\t\t\t\treturn yymmdd\n\t\t\t\t} else if (this.type === 'time') {\n\t\t\t\t\treturn hhmmss\n\t\t\t\t} else {\n\t\t\t\t\treturn yymmdd + ' ' + hhmmss\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 初始化返回值，并抛出 change 事件\n\t\t\t */\n\t\t\tinitTime(emit = true) {\n\t\t\t\tthis.time = this.createDomSting()\n\t\t\t\tif (!emit) return\n\t\t\t\tif (this.returnType === 'timestamp' && this.type !== 'time') {\n\t\t\t\t\tthis.$emit('change', this.createTimeStamp(this.time))\n\t\t\t\t\tthis.$emit('input', this.createTimeStamp(this.time))\n\t\t\t\t\tthis.$emit('update:modelValue', this.createTimeStamp(this.time))\n\t\t\t\t} else {\n\t\t\t\t\tthis.$emit('change', this.time)\n\t\t\t\t\tthis.$emit('input', this.time)\n\t\t\t\t\tthis.$emit('update:modelValue', this.time)\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 用户选择日期或时间更新 data\n\t\t\t * @param {Object} e\n\t\t\t */\n\t\t\tbindDateChange(e) {\n\t\t\t\tconst val = e.detail.value\n\t\t\t\tthis.year = this.years[val[0]]\n\t\t\t\tthis.month = this.months[val[1]]\n\t\t\t\tthis.day = this.days[val[2]]\n\t\t\t},\n\t\t\tbindTimeChange(e) {\n\t\t\t\tconst val = e.detail.value\n\t\t\t\tthis.hour = this.hours[val[0]]\n\t\t\t\tthis.minute = this.minutes[val[1]]\n\t\t\t\tthis.second = this.seconds[val[2]]\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 初始化弹出层\n\t\t\t */\n\t\t\tinitTimePicker() {\n\t\t\t\tif (this.disabled) return\n\t\t\t\tconst value = this.fixIosDateFormat(this.value)\n\t\t\t\tthis.initPickerValue(value)\n\t\t\t\tthis.visible = !this.visible\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 触发或关闭弹框\n\t\t\t */\n\t\t\ttiggerTimePicker(e) {\n\t\t\t\tthis.visible = !this.visible\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 用户点击“清空”按钮，清空当前值\n\t\t\t */\n\t\t\tclearTime() {\n\t\t\t\tthis.time = ''\n\t\t\t\tthis.$emit('change', this.time)\n\t\t\t\tthis.$emit('input', this.time)\n\t\t\t\tthis.$emit('update:modelValue', this.time)\n\t\t\t\tthis.tiggerTimePicker()\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 用户点击“确定”按钮\n\t\t\t */\n\t\t\tsetTime() {\n\t\t\t\tthis.initTime()\n\t\t\t\tthis.tiggerTimePicker()\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\">\n\t$uni-primary: #007aff !default;\n\n\t.uni-datetime-picker {\n\t\t/* #ifndef APP-NVUE */\n\t\t/* width: 100%; */\n\t\t/* #endif */\n\t}\n\n\t.uni-datetime-picker-view {\n\t\theight: 130px;\n\t\twidth: 270px;\n\t\t/* #ifndef APP-NVUE */\n\t\tcursor: pointer;\n\t\t/* #endif */\n\t}\n\n\t.uni-datetime-picker-item {\n\t\theight: 50px;\n\t\tline-height: 50px;\n\t\ttext-align: center;\n\t\tfont-size: 14px;\n\t}\n\n\t.uni-datetime-picker-btn {\n\t\tmargin-top: 60px;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\tcursor: pointer;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tjustify-content: space-between;\n\t}\n\n\t.uni-datetime-picker-btn-text {\n\t\tfont-size: 14px;\n\t\tcolor: $uni-primary;\n\t}\n\n\t.uni-datetime-picker-btn-group {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t}\n\n\t.uni-datetime-picker-cancel {\n\t\tmargin-right: 30px;\n\t}\n\n\t.uni-datetime-picker-mask {\n\t\tposition: fixed;\n\t\tbottom: 0px;\n\t\ttop: 0px;\n\t\tleft: 0px;\n\t\tright: 0px;\n\t\tbackground-color: rgba(0, 0, 0, 0.4);\n\t\ttransition-duration: 0.3s;\n\t\tz-index: 998;\n\t}\n\n\t.uni-datetime-picker-popup {\n\t\tborder-radius: 8px;\n\t\tpadding: 30px;\n\t\twidth: 270px;\n\t\t/* #ifdef APP-NVUE */\n\t\theight: 500px;\n\t\t/* #endif */\n\t\t/* #ifdef APP-NVUE */\n\t\twidth: 330px;\n\t\t/* #endif */\n\t\tbackground-color: #fff;\n\t\tposition: fixed;\n\t\ttop: 50%;\n\t\tleft: 50%;\n\t\ttransform: translate(-50%, -50%);\n\t\ttransition-duration: 0.3s;\n\t\tz-index: 999;\n\t}\n\n\t.fix-nvue-height {\n\t\t/* #ifdef APP-NVUE */\n\t\theight: 330px;\n\t\t/* #endif */\n\t}\n\n\t.uni-datetime-picker-time {\n\t\tcolor: grey;\n\t}\n\n\t.uni-datetime-picker-column {\n\t\theight: 50px;\n\t}\n\n\t.uni-datetime-picker-timebox {\n\n\t\tborder: 1px solid #E5E5E5;\n\t\tborder-radius: 5px;\n\t\tpadding: 7px 10px;\n\t\t/* #ifndef APP-NVUE */\n\t\tbox-sizing: border-box;\n\t\tcursor: pointer;\n\t\t/* #endif */\n\t}\n\n\t.uni-datetime-picker-timebox-pointer {\n\t\t/* #ifndef APP-NVUE */\n\t\tcursor: pointer;\n\t\t/* #endif */\n\t}\n\n\n\t.uni-datetime-picker-disabled {\n\t\topacity: 0.4;\n\t\t/* #ifdef H5 */\n\t\tcursor: not-allowed !important;\n\t\t/* #endif */\n\t}\n\n\t.uni-datetime-picker-text {\n\t\tfont-size: 14px;\n\t}\n\n\t.uni-datetime-picker-sign {\n\t\tposition: absolute;\n\t\ttop: 53px;\n\t\t/* 减掉 10px 的元素高度，兼容nvue */\n\t\tcolor: #999;\n\t\t/* #ifdef APP-NVUE */\n\t\tfont-size: 16px;\n\t\t/* #endif */\n\t}\n\n\t.sign-left {\n\t\tleft: 86px;\n\t}\n\n\t.sign-right {\n\t\tright: 86px;\n\t}\n\n\t.sign-center {\n\t\tleft: 135px;\n\t}\n\n\t.uni-datetime-picker__container-box {\n\t\tposition: relative;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tmargin-top: 40px;\n\t}\n\n\t.time-hide-second {\n\t\twidth: 180px;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue",
    "content": "<template>\n\t<view class=\"uni-date\">\n\t\t<view class=\"uni-date-editor\" @click=\"show\">\n\t\t\t<slot>\n\t\t\t\t<view class=\"uni-date-editor--x\" :class=\"{'uni-date-editor--x__disabled': disabled,\n\t\t'uni-date-x--border': border}\">\n\t\t\t\t\t<view v-if=\"!isRange\" class=\"uni-date-x uni-date-single\">\n\t\t\t\t\t\t<uni-icons type=\"calendar\" color=\"#c0c4cc\" size=\"22\"></uni-icons>\n\t\t\t\t\t\t<input class=\"uni-date__x-input\" type=\"text\" v-model=\"singleVal\"\n\t\t\t\t\t\t\t:placeholder=\"singlePlaceholderText\" :disabled=\"true\" />\n\t\t\t\t\t</view>\n\t\t\t\t\t<view v-else class=\"uni-date-x uni-date-range\">\n\t\t\t\t\t\t<uni-icons type=\"calendar\" color=\"#c0c4cc\" size=\"22\"></uni-icons>\n\t\t\t\t\t\t<input class=\"uni-date__x-input t-c\" type=\"text\" v-model=\"range.startDate\"\n\t\t\t\t\t\t\t:placeholder=\"startPlaceholderText\" :disabled=\"true\" />\n\t\t\t\t\t\t<slot>\n\t\t\t\t\t\t\t<view class=\"\">{{rangeSeparator}}</view>\n\t\t\t\t\t\t</slot>\n\t\t\t\t\t\t<input class=\"uni-date__x-input t-c\" type=\"text\" v-model=\"range.endDate\"\n\t\t\t\t\t\t\t:placeholder=\"endPlaceholderText\" :disabled=\"true\" />\n\t\t\t\t\t</view>\n\t\t\t\t\t<view v-if=\"showClearIcon\" class=\"uni-date__icon-clear\" @click.stop=\"clear\">\n\t\t\t\t\t\t<uni-icons type=\"clear\" color=\"#c0c4cc\" size=\"24\"></uni-icons>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</slot>\n\t\t</view>\n\n\t\t<view v-show=\"popup\" class=\"uni-date-mask\" @click=\"close\"></view>\n\t\t<view v-if=\"!isPhone\" ref=\"datePicker\" v-show=\"popup\" class=\"uni-date-picker__container\">\n\t\t\t<view v-if=\"!isRange\" class=\"uni-date-single--x\" :style=\"popover\">\n\t\t\t\t<view class=\"uni-popper__arrow\"></view>\n\t\t\t\t<view v-if=\"hasTime\" class=\"uni-date-changed popup-x-header\">\n\t\t\t\t\t<input class=\"uni-date__input t-c\" type=\"text\" v-model=\"tempSingleDate\"\n\t\t\t\t\t\t:placeholder=\"selectDateText\" />\n\t\t\t\t\t<time-picker type=\"time\" v-model=\"time\" :border=\"false\" :disabled=\"!tempSingleDate\"\n\t\t\t\t\t\t:start=\"reactStartTime\" :end=\"reactEndTime\" :hideSecond=\"hideSecond\" style=\"width: 100%;\">\n\t\t\t\t\t\t<input class=\"uni-date__input t-c\" type=\"text\" v-model=\"time\" :placeholder=\"selectTimeText\"\n\t\t\t\t\t\t\t:disabled=\"!tempSingleDate\" />\n\t\t\t\t\t</time-picker>\n\t\t\t\t</view>\n\t\t\t\t<calendar ref=\"pcSingle\" :showMonth=\"false\" :start-date=\"caleRange.startDate\"\n\t\t\t\t\t:end-date=\"caleRange.endDate\" :date=\"defSingleDate\" @change=\"singleChange\"\n\t\t\t\t\tstyle=\"padding: 0 8px;\" />\n\t\t\t\t<view v-if=\"hasTime\" class=\"popup-x-footer\">\n\t\t\t\t\t<!-- <text class=\"\">此刻</text> -->\n\t\t\t\t\t<text class=\"confirm\" @click=\"confirmSingleChange\">{{okText}}</text>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"uni-date-popper__arrow\"></view>\n\t\t\t</view>\n\n\t\t\t<view v-else class=\"uni-date-range--x\" :style=\"popover\">\n\t\t\t\t<view class=\"uni-popper__arrow\"></view>\n\t\t\t\t<view v-if=\"hasTime\" class=\"popup-x-header uni-date-changed\">\n\t\t\t\t\t<view class=\"popup-x-header--datetime\">\n\t\t\t\t\t\t<input class=\"uni-date__input uni-date-range__input\" type=\"text\" v-model=\"tempRange.startDate\"\n\t\t\t\t\t\t\t:placeholder=\"startDateText\" />\n\t\t\t\t\t\t<time-picker type=\"time\" v-model=\"tempRange.startTime\" :start=\"reactStartTime\" :border=\"false\"\n\t\t\t\t\t\t\t:disabled=\"!tempRange.startDate\" :hideSecond=\"hideSecond\">\n\t\t\t\t\t\t\t<input class=\"uni-date__input uni-date-range__input\" type=\"text\"\n\t\t\t\t\t\t\t\tv-model=\"tempRange.startTime\" :placeholder=\"startTimeText\"\n\t\t\t\t\t\t\t\t:disabled=\"!tempRange.startDate\" />\n\t\t\t\t\t\t</time-picker>\n\t\t\t\t\t</view>\n\t\t\t\t\t<uni-icons type=\"arrowthinright\" color=\"#999\" style=\"line-height: 40px;\"></uni-icons>\n\t\t\t\t\t<view class=\"popup-x-header--datetime\">\n\t\t\t\t\t\t<input class=\"uni-date__input uni-date-range__input\" type=\"text\" v-model=\"tempRange.endDate\"\n\t\t\t\t\t\t\t:placeholder=\"endDateText\" />\n\t\t\t\t\t\t<time-picker type=\"time\" v-model=\"tempRange.endTime\" :end=\"reactEndTime\" :border=\"false\"\n\t\t\t\t\t\t\t:disabled=\"!tempRange.endDate\" :hideSecond=\"hideSecond\">\n\t\t\t\t\t\t\t<input class=\"uni-date__input uni-date-range__input\" type=\"text\" v-model=\"tempRange.endTime\"\n\t\t\t\t\t\t\t\t:placeholder=\"endTimeText\" :disabled=\"!tempRange.endDate\" />\n\t\t\t\t\t\t</time-picker>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"popup-x-body\">\n\t\t\t\t\t<calendar ref=\"left\" :showMonth=\"false\" :start-date=\"caleRange.startDate\"\n\t\t\t\t\t\t:end-date=\"caleRange.endDate\" :range=\"true\" @change=\"leftChange\" :pleStatus=\"endMultipleStatus\"\n\t\t\t\t\t\t@firstEnterCale=\"updateRightCale\" @monthSwitch=\"leftMonthSwitch\" style=\"padding: 0 8px;\" />\n\t\t\t\t\t<calendar ref=\"right\" :showMonth=\"false\" :start-date=\"caleRange.startDate\"\n\t\t\t\t\t\t:end-date=\"caleRange.endDate\" :range=\"true\" @change=\"rightChange\"\n\t\t\t\t\t\t:pleStatus=\"startMultipleStatus\" @firstEnterCale=\"updateLeftCale\"\n\t\t\t\t\t\t@monthSwitch=\"rightMonthSwitch\" style=\"padding: 0 8px;border-left: 1px solid #F1F1F1;\" />\n\t\t\t\t</view>\n\t\t\t\t<view v-if=\"hasTime\" class=\"popup-x-footer\">\n\t\t\t\t\t<text class=\"\" @click=\"clear\">{{clearText}}</text>\n\t\t\t\t\t<text class=\"confirm\" @click=\"confirmRangeChange\">{{okText}}</text>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t\t<calendar v-show=\"isPhone\" ref=\"mobile\" :clearDate=\"false\" :date=\"defSingleDate\" :defTime=\"reactMobDefTime\"\n\t\t\t:start-date=\"caleRange.startDate\" :end-date=\"caleRange.endDate\" :selectableTimes=\"mobSelectableTime\"\n\t\t\t:pleStatus=\"endMultipleStatus\" :showMonth=\"false\" :range=\"isRange\" :typeHasTime=\"hasTime\" :insert=\"false\"\n\t\t\t:hideSecond=\"hideSecond\" @confirm=\"mobileChange\" @maskClose=\"close\" />\n\t</view>\n</template>\n<script>\n\t/**\n\t * DatetimePicker 时间选择器\n\t * @description 同时支持 PC 和移动端使用日历选择日期和日期范围\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=3962\n\t * @property {String} type 选择器类型\n\t * @property {String|Number|Array|Date} value 绑定值\n\t * @property {String} placeholder 单选择时的占位内容\n\t * @property {String} start 起始时间\n\t * @property {String} end 终止时间\n\t * @property {String} start-placeholder 范围选择时开始日期的占位内容\n\t * @property {String} end-placeholder 范围选择时结束日期的占位内容\n\t * @property {String} range-separator 选择范围时的分隔符\n\t * @property {Boolean} border = [true|false] 是否有边框\n\t * @property {Boolean} disabled = [true|false] 是否禁用\n\t * @property {Boolean} clearIcon = [true|false] 是否显示清除按钮（仅PC端适用）\n\t * @event {Function} change 确定日期时触发的事件\n\t * @event {Function} show 打开弹出层\n\t * @event {Function} close 关闭弹出层\n\t * @event {Function} clear 清除上次选中的状态和值\n\t **/\n\timport calendar from './calendar.vue'\n\timport timePicker from './time-picker.vue'\n\timport {\n\t\tinitVueI18n\n\t} from '@dcloudio/uni-i18n'\n\timport messages from './i18n/index.js'\n\tconst {\n\t\tt\n\t} = initVueI18n(messages)\n\n\texport default {\n\t\tname: 'UniDatetimePicker',\n\t\toptions: {\n\t\t\tvirtualHost: true\n\t\t},\n\t\tcomponents: {\n\t\t\tcalendar,\n\t\t\ttimePicker\n\t\t},\n\t\tinject: {\n\t\t\tform: {\n\t\t\t\tfrom: 'uniForm',\n\t\t\t\tdefault: null\n\t\t\t},\n\t\t\tformItem: {\n\t\t\t\tfrom: 'uniFormItem',\n\t\t\t\tdefault: null\n\t\t\t},\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisRange: false,\n\t\t\t\thasTime: false,\n\t\t\t\tmobileRange: false,\n\t\t\t\t// 单选\n\t\t\t\tsingleVal: '',\n\t\t\t\ttempSingleDate: '',\n\t\t\t\tdefSingleDate: '',\n\t\t\t\ttime: '',\n\t\t\t\t// 范围选\n\t\t\t\tcaleRange: {\n\t\t\t\t\tstartDate: '',\n\t\t\t\t\tstartTime: '',\n\t\t\t\t\tendDate: '',\n\t\t\t\t\tendTime: ''\n\t\t\t\t},\n\t\t\t\trange: {\n\t\t\t\t\tstartDate: '',\n\t\t\t\t\t// startTime: '',\n\t\t\t\t\tendDate: '',\n\t\t\t\t\t// endTime: ''\n\t\t\t\t},\n\t\t\t\ttempRange: {\n\t\t\t\t\tstartDate: '',\n\t\t\t\t\tstartTime: '',\n\t\t\t\t\tendDate: '',\n\t\t\t\t\tendTime: ''\n\t\t\t\t},\n\t\t\t\t// 左右日历同步数据\n\t\t\t\tstartMultipleStatus: {\n\t\t\t\t\tbefore: '',\n\t\t\t\t\tafter: '',\n\t\t\t\t\tdata: [],\n\t\t\t\t\tfulldate: ''\n\t\t\t\t},\n\t\t\t\tendMultipleStatus: {\n\t\t\t\t\tbefore: '',\n\t\t\t\t\tafter: '',\n\t\t\t\t\tdata: [],\n\t\t\t\t\tfulldate: ''\n\t\t\t\t},\n\t\t\t\tvisible: false,\n\t\t\t\tpopup: false,\n\t\t\t\tpopover: null,\n\t\t\t\tisEmitValue: false,\n\t\t\t\tisPhone: false,\n\t\t\t\tisFirstShow: true,\n\t\t\t}\n\t\t},\n\t\tprops: {\n\t\t\ttype: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'datetime'\n\t\t\t},\n\t\t\tvalue: {\n\t\t\t\ttype: [String, Number, Array, Date],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tmodelValue: {\n\t\t\t\ttype: [String, Number, Array, Date],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tstart: {\n\t\t\t\ttype: [Number, String],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tend: {\n\t\t\t\ttype: [Number, String],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\treturnType: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'string'\n\t\t\t},\n\t\t\tplaceholder: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tstartPlaceholder: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tendPlaceholder: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\trangeSeparator: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: '-'\n\t\t\t},\n\t\t\tborder: {\n\t\t\t\ttype: [Boolean],\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\tdisabled: {\n\t\t\t\ttype: [Boolean],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tclearIcon: {\n\t\t\t\ttype: [Boolean],\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\thideSecond: {\n\t\t\t\ttype: [Boolean],\n\t\t\t\tdefault: false\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\ttype: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tif (newVal.indexOf('time') !== -1) {\n\t\t\t\t\t\tthis.hasTime = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.hasTime = false\n\t\t\t\t\t}\n\t\t\t\t\tif (newVal.indexOf('range') !== -1) {\n\t\t\t\t\t\tthis.isRange = true\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.isRange = false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t// #ifndef VUE3\n\t\t\tvalue: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tif (this.isEmitValue) {\n\t\t\t\t\t\tthis.isEmitValue = false\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tthis.initPicker(newVal)\n\t\t\t\t}\n\t\t\t},\n\t\t\t// #endif\n\t\t\t// #ifdef VUE3\n\t\t\tmodelValue: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tif (this.isEmitValue) {\n\t\t\t\t\t\tthis.isEmitValue = false\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tthis.initPicker(newVal)\n\t\t\t\t}\n\t\t\t},\n\t\t\t// #endif\n\t\t\tstart: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tif (!newVal) return\n\t\t\t\t\tconst {\n\t\t\t\t\t\tdefDate,\n\t\t\t\t\t\tdefTime\n\t\t\t\t\t} = this.parseDate(newVal)\n\t\t\t\t\tthis.caleRange.startDate = defDate\n\t\t\t\t\tif (this.hasTime) {\n\t\t\t\t\t\tthis.caleRange.startTime = defTime\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tend: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal, oldVal) {\n\t\t\t\t\tif (!newVal) return\n\t\t\t\t\tconst {\n\t\t\t\t\t\tdefDate,\n\t\t\t\t\t\tdefTime\n\t\t\t\t\t} = this.parseDate(newVal)\n\t\t\t\t\tthis.caleRange.endDate = defDate\n\t\t\t\t\tif (this.hasTime) {\n\t\t\t\t\t\tthis.caleRange.endTime = defTime\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tcomputed: {\n\t\t\treactStartTime() {\n\t\t\t\tconst activeDate = this.isRange ? this.tempRange.startDate : this.tempSingleDate\n\t\t\t\tconst res = activeDate === this.caleRange.startDate ? this.caleRange.startTime : ''\n\t\t\t\treturn res\n\t\t\t},\n\t\t\treactEndTime() {\n\t\t\t\tconst activeDate = this.isRange ? this.tempRange.endDate : this.tempSingleDate\n\t\t\t\tconst res = activeDate === this.caleRange.endDate ? this.caleRange.endTime : ''\n\t\t\t\treturn res\n\t\t\t},\n\t\t\treactMobDefTime() {\n\t\t\t\tconst times = {\n\t\t\t\t\tstart: this.tempRange.startTime,\n\t\t\t\t\tend: this.tempRange.endTime\n\t\t\t\t}\n\t\t\t\treturn this.isRange ? times : this.time\n\t\t\t},\n\t\t\tmobSelectableTime() {\n\t\t\t\treturn {\n\t\t\t\t\tstart: this.caleRange.startTime,\n\t\t\t\t\tend: this.caleRange.endTime\n\t\t\t\t}\n\t\t\t},\n\t\t\tdatePopupWidth() {\n\t\t\t\t// todo\n\t\t\t\treturn this.isRange ? 653 : 301\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * for i18n\n\t\t\t */\n\t\t\tsinglePlaceholderText() {\n\t\t\t\treturn this.placeholder || (this.type === 'date' ? this.selectDateText : t(\n\t\t\t\t\t\"uni-datetime-picker.selectDateTime\"))\n\t\t\t},\n\t\t\tstartPlaceholderText() {\n\t\t\t\treturn this.startPlaceholder || this.startDateText\n\t\t\t},\n\t\t\tendPlaceholderText() {\n\t\t\t\treturn this.endPlaceholder || this.endDateText\n\t\t\t},\n\t\t\tselectDateText() {\n\t\t\t\treturn t(\"uni-datetime-picker.selectDate\")\n\t\t\t},\n\t\t\tselectTimeText() {\n\t\t\t\treturn t(\"uni-datetime-picker.selectTime\")\n\t\t\t},\n\t\t\tstartDateText() {\n\t\t\t\treturn this.startPlaceholder || t(\"uni-datetime-picker.startDate\")\n\t\t\t},\n\t\t\tstartTimeText() {\n\t\t\t\treturn t(\"uni-datetime-picker.startTime\")\n\t\t\t},\n\t\t\tendDateText() {\n\t\t\t\treturn this.endPlaceholder || t(\"uni-datetime-picker.endDate\")\n\t\t\t},\n\t\t\tendTimeText() {\n\t\t\t\treturn t(\"uni-datetime-picker.endTime\")\n\t\t\t},\n\t\t\tokText() {\n\t\t\t\treturn t(\"uni-datetime-picker.ok\")\n\t\t\t},\n\t\t\tclearText() {\n\t\t\t\treturn t(\"uni-datetime-picker.clear\")\n\t\t\t},\n\t\t\tshowClearIcon() {\n\t\t\t\tconst {\n\t\t\t\t\tclearIcon,\n\t\t\t\t\tdisabled,\n\t\t\t\t\tsingleVal,\n\t\t\t\t\trange\n\t\t\t\t} = this\n\t\t\t\tconst bool = clearIcon && !disabled && (singleVal || (range.startDate && range.endDate))\n\t\t\t\treturn bool\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\t// if (this.form && this.formItem) {\n\t\t\t// \tthis.$watch('formItem.errMsg', (newVal) => {\n\t\t\t// \t\tthis.localMsg = newVal\n\t\t\t// \t})\n\t\t\t// }\n\t\t},\n\t\tmounted() {\n\t\t\tthis.platform()\n\t\t},\n\t\tmethods: {\n\t\t\tinitPicker(newVal) {\n\t\t\t\tif (!newVal || Array.isArray(newVal) && !newVal.length) {\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.clear(false)\n\t\t\t\t\t})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif (!Array.isArray(newVal) && !this.isRange) {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tdefDate,\n\t\t\t\t\t\tdefTime\n\t\t\t\t\t} = this.parseDate(newVal)\n\t\t\t\t\tthis.singleVal = defDate\n\t\t\t\t\tthis.tempSingleDate = defDate\n\t\t\t\t\tthis.defSingleDate = defDate\n\t\t\t\t\tif (this.hasTime) {\n\t\t\t\t\t\tthis.singleVal = defDate + ' ' + defTime\n\t\t\t\t\t\tthis.time = defTime\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst [before, after] = newVal\n\t\t\t\t\tif (!before && !after) return\n\t\t\t\t\tconst defBefore = this.parseDate(before)\n\t\t\t\t\tconst defAfter = this.parseDate(after)\n\t\t\t\t\tconst startDate = defBefore.defDate\n\t\t\t\t\tconst endDate = defAfter.defDate\n\t\t\t\t\tthis.range.startDate = this.tempRange.startDate = startDate\n\t\t\t\t\tthis.range.endDate = this.tempRange.endDate = endDate\n\n\t\t\t\t\tif (this.hasTime) {\n\t\t\t\t\t\tthis.range.startDate = defBefore.defDate + ' ' + defBefore.defTime\n\t\t\t\t\t\tthis.range.endDate = defAfter.defDate + ' ' + defAfter.defTime\n\t\t\t\t\t\tthis.tempRange.startTime = defBefore.defTime\n\t\t\t\t\t\tthis.tempRange.endTime = defAfter.defTime\n\t\t\t\t\t}\n\t\t\t\t\tconst defaultRange = {\n\t\t\t\t\t\tbefore: defBefore.defDate,\n\t\t\t\t\t\tafter: defAfter.defDate\n\t\t\t\t\t}\n\t\t\t\t\tthis.startMultipleStatus = Object.assign({}, this.startMultipleStatus, defaultRange, {\n\t\t\t\t\t\twhich: 'right'\n\t\t\t\t\t})\n\t\t\t\t\tthis.endMultipleStatus = Object.assign({}, this.endMultipleStatus, defaultRange, {\n\t\t\t\t\t\twhich: 'left'\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t\tupdateLeftCale(e) {\n\t\t\t\tconst left = this.$refs.left\n\t\t\t\t// 设置范围选\n\t\t\t\tleft.cale.setHoverMultiple(e.after)\n\t\t\t\tleft.setDate(this.$refs.left.nowDate.fullDate)\n\t\t\t},\n\t\t\tupdateRightCale(e) {\n\t\t\t\tconst right = this.$refs.right\n\t\t\t\t// 设置范围选\n\t\t\t\tright.cale.setHoverMultiple(e.after)\n\t\t\t\tright.setDate(this.$refs.right.nowDate.fullDate)\n\t\t\t},\n\t\t\tplatform() {\n\t\t\t\tconst systemInfo = uni.getSystemInfoSync()\n\t\t\t\tthis.isPhone = systemInfo.windowWidth <= 500\n\t\t\t\tthis.windowWidth = systemInfo.windowWidth\n\t\t\t},\n\t\t\tshow(event) {\n\t\t\t\tif (this.disabled) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.platform()\n\t\t\t\tif (this.isPhone) {\n\t\t\t\t\tthis.$refs.mobile.open()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.popover = {\n\t\t\t\t\ttop: '10px'\n\t\t\t\t}\n\t\t\t\tconst dateEditor = uni.createSelectorQuery().in(this).select(\".uni-date-editor\")\n\t\t\t\tdateEditor.boundingClientRect(rect => {\n\t\t\t\t\tif (this.windowWidth - rect.left < this.datePopupWidth) {\n\t\t\t\t\t\tthis.popover.right = 0\n\t\t\t\t\t}\n\t\t\t\t}).exec()\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.popup = !this.popup\n\t\t\t\t\tif (!this.isPhone && this.isRange && this.isFirstShow) {\n\t\t\t\t\t\tthis.isFirstShow = false\n\t\t\t\t\t\tconst {\n\t\t\t\t\t\t\tstartDate,\n\t\t\t\t\t\t\tendDate\n\t\t\t\t\t\t} = this.range\n\t\t\t\t\t\tif (startDate && endDate) {\n\t\t\t\t\t\t\tif (this.diffDate(startDate, endDate) < 30) {\n\t\t\t\t\t\t\t\tthis.$refs.right.next()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.$refs.right.next()\n\t\t\t\t\t\t\tthis.$refs.right.cale.lastHover = false\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t}, 50)\n\t\t\t},\n\n\t\t\tclose() {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.popup = false\n\t\t\t\t\tthis.$emit('maskClick', this.value)\n\t\t\t\t\tthis.$refs.mobile.close()\n\t\t\t\t}, 20)\n\t\t\t},\n\t\t\tsetEmit(value) {\n\t\t\t\tif (this.returnType === \"timestamp\" || this.returnType === \"date\") {\n\t\t\t\t\tif (!Array.isArray(value)) {\n\t\t\t\t\t\tif (!this.hasTime) {\n\t\t\t\t\t\t\tvalue = value + ' ' + '00:00:00'\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvalue = this.createTimestamp(value)\n\t\t\t\t\t\tif (this.returnType === \"date\") {\n\t\t\t\t\t\t\tvalue = new Date(value)\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (!this.hasTime) {\n\t\t\t\t\t\t\tvalue[0] = value[0] + ' ' + '00:00:00'\n\t\t\t\t\t\t\tvalue[1] = value[1] + ' ' + '00:00:00'\n\t\t\t\t\t\t}\n\t\t\t\t\t\tvalue[0] = this.createTimestamp(value[0])\n\t\t\t\t\t\tvalue[1] = this.createTimestamp(value[1])\n\t\t\t\t\t\tif (this.returnType === \"date\") {\n\t\t\t\t\t\t\tvalue[0] = new Date(value[0])\n\t\t\t\t\t\t\tvalue[1] = new Date(value[1])\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\n\t\t\t\tthis.$emit('change', value)\n\t\t\t\tthis.$emit('input', value)\n\t\t\t\tthis.$emit('update:modelValue', value)\n\t\t\t\tthis.isEmitValue = true\n\t\t\t},\n\t\t\tcreateTimestamp(date) {\n\t\t\t\tdate = this.fixIosDateFormat(date)\n\t\t\t\treturn Date.parse(new Date(date))\n\t\t\t},\n\t\t\tsingleChange(e) {\n\t\t\t\tthis.tempSingleDate = e.fulldate\n\t\t\t\tif (this.hasTime) return\n\t\t\t\tthis.confirmSingleChange()\n\t\t\t},\n\n\t\t\tconfirmSingleChange() {\n\t\t\t\tif (!this.tempSingleDate) {\n\t\t\t\t\tthis.popup = false\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif (this.hasTime) {\n\t\t\t\t\tthis.singleVal = this.tempSingleDate + ' ' + (this.time ? this.time : '00:00:00')\n\t\t\t\t} else {\n\t\t\t\t\tthis.singleVal = this.tempSingleDate\n\t\t\t\t}\n\t\t\t\tthis.setEmit(this.singleVal)\n\t\t\t\tthis.popup = false\n\t\t\t},\n\n\t\t\tleftChange(e) {\n\t\t\t\tconst {\n\t\t\t\t\tbefore,\n\t\t\t\t\tafter\n\t\t\t\t} = e.range\n\t\t\t\tthis.rangeChange(before, after)\n\t\t\t\tconst obj = {\n\t\t\t\t\tbefore: e.range.before,\n\t\t\t\t\tafter: e.range.after,\n\t\t\t\t\tdata: e.range.data,\n\t\t\t\t\tfulldate: e.fulldate\n\t\t\t\t}\n\t\t\t\tthis.startMultipleStatus = Object.assign({}, this.startMultipleStatus, obj)\n\t\t\t},\n\n\t\t\trightChange(e) {\n\t\t\t\tconst {\n\t\t\t\t\tbefore,\n\t\t\t\t\tafter\n\t\t\t\t} = e.range\n\t\t\t\tthis.rangeChange(before, after)\n\t\t\t\tconst obj = {\n\t\t\t\t\tbefore: e.range.before,\n\t\t\t\t\tafter: e.range.after,\n\t\t\t\t\tdata: e.range.data,\n\t\t\t\t\tfulldate: e.fulldate\n\t\t\t\t}\n\t\t\t\tthis.endMultipleStatus = Object.assign({}, this.endMultipleStatus, obj)\n\t\t\t},\n\n\t\t\tmobileChange(e) {\n\t\t\t\tif (this.isRange) {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tbefore,\n\t\t\t\t\t\tafter\n\t\t\t\t\t} = e.range\n\t\t\t\t\tthis.handleStartAndEnd(before, after, true)\n\t\t\t\t\tif (this.hasTime) {\n\t\t\t\t\t\tconst {\n\t\t\t\t\t\t\tstartTime,\n\t\t\t\t\t\t\tendTime\n\t\t\t\t\t\t} = e.timeRange\n\t\t\t\t\t\tthis.tempRange.startTime = startTime\n\t\t\t\t\t\tthis.tempRange.endTime = endTime\n\t\t\t\t\t}\n\t\t\t\t\tthis.confirmRangeChange()\n\n\t\t\t\t} else {\n\t\t\t\t\tif (this.hasTime) {\n\t\t\t\t\t\tthis.singleVal = e.fulldate + ' ' + e.time\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.singleVal = e.fulldate\n\t\t\t\t\t}\n\t\t\t\t\tthis.setEmit(this.singleVal)\n\t\t\t\t}\n\t\t\t\tthis.$refs.mobile.close()\n\t\t\t},\n\n\t\t\trangeChange(before, after) {\n\t\t\t\tif (!(before && after)) return\n\t\t\t\tthis.handleStartAndEnd(before, after, true)\n\t\t\t\tif (this.hasTime) return\n\t\t\t\tthis.confirmRangeChange()\n\t\t\t},\n\n\t\t\tconfirmRangeChange() {\n\t\t\t\tif (!this.tempRange.startDate && !this.tempRange.endDate) {\n\t\t\t\t\tthis.popup = false\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tlet start, end\n\t\t\t\tif (!this.hasTime) {\n\t\t\t\t\tstart = this.range.startDate = this.tempRange.startDate\n\t\t\t\t\tend = this.range.endDate = this.tempRange.endDate\n\t\t\t\t} else {\n\t\t\t\t\tstart = this.range.startDate = this.tempRange.startDate + ' ' +\n\t\t\t\t\t\t(this.tempRange.startTime ? this.tempRange.startTime : '00:00:00')\n\t\t\t\t\tend = this.range.endDate = this.tempRange.endDate + ' ' +\n\t\t\t\t\t\t(this.tempRange.endTime ? this.tempRange.endTime : '00:00:00')\n\t\t\t\t}\n\t\t\t\tconst displayRange = [start, end]\n\t\t\t\tthis.setEmit(displayRange)\n\t\t\t\tthis.popup = false\n\t\t\t},\n\n\t\t\thandleStartAndEnd(before, after, temp = false) {\n\t\t\t\tif (!(before && after)) return\n\t\t\t\tconst type = temp ? 'tempRange' : 'range'\n\t\t\t\tif (this.dateCompare(before, after)) {\n\t\t\t\t\tthis[type].startDate = before\n\t\t\t\t\tthis[type].endDate = after\n\t\t\t\t} else {\n\t\t\t\t\tthis[type].startDate = after\n\t\t\t\t\tthis[type].endDate = before\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 比较时间大小\n\t\t\t */\n\t\t\tdateCompare(startDate, endDate) {\n\t\t\t\t// 计算截止时间\n\t\t\t\tstartDate = new Date(startDate.replace('-', '/').replace('-', '/'))\n\t\t\t\t// 计算详细项的截止时间\n\t\t\t\tendDate = new Date(endDate.replace('-', '/').replace('-', '/'))\n\t\t\t\tif (startDate <= endDate) {\n\t\t\t\t\treturn true\n\t\t\t\t} else {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t/**\n\t\t\t * 比较时间差\n\t\t\t */\n\t\t\tdiffDate(startDate, endDate) {\n\t\t\t\t// 计算截止时间\n\t\t\t\tstartDate = new Date(startDate.replace('-', '/').replace('-', '/'))\n\t\t\t\t// 计算详细项的截止时间\n\t\t\t\tendDate = new Date(endDate.replace('-', '/').replace('-', '/'))\n\t\t\t\tconst diff = (endDate - startDate) / (24 * 60 * 60 * 1000)\n\t\t\t\treturn Math.abs(diff)\n\t\t\t},\n\n\t\t\tclear(needEmit = true) {\n\t\t\t\tif (!this.isRange) {\n\t\t\t\t\tthis.singleVal = ''\n\t\t\t\t\tthis.tempSingleDate = ''\n\t\t\t\t\tthis.time = ''\n\t\t\t\t\tif (this.isPhone) {\n\t\t\t\t\t\tthis.$refs.mobile && this.$refs.mobile.clearCalender()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.$refs.pcSingle && this.$refs.pcSingle.clearCalender()\n\t\t\t\t\t}\n\t\t\t\t\tif (needEmit) {\n\t\t\t\t\t\t// 校验规则\n\t\t\t\t\t\t// if(this.form  && this.formItem){\n\t\t\t\t\t\t// \tconst {\n\t\t\t\t\t\t// \t\tvalidateTrigger\n\t\t\t\t\t\t// \t} = this.form\n\t\t\t\t\t\t// \tif (validateTrigger === 'blur') {\n\t\t\t\t\t\t// \t\tthis.formItem.onFieldChange()\n\t\t\t\t\t\t// \t}\n\t\t\t\t\t\t// }\n\t\t\t\t\t\tthis.$emit('change', '')\n\t\t\t\t\t\tthis.$emit('input', '')\n\t\t\t\t\t\tthis.$emit('update:modelValue', '')\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.range.startDate = ''\n\t\t\t\t\tthis.range.endDate = ''\n\t\t\t\t\tthis.tempRange.startDate = ''\n\t\t\t\t\tthis.tempRange.startTime = ''\n\t\t\t\t\tthis.tempRange.endDate = ''\n\t\t\t\t\tthis.tempRange.endTime = ''\n\t\t\t\t\tif (this.isPhone) {\n\t\t\t\t\t\tthis.$refs.mobile && this.$refs.mobile.clearCalender()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.$refs.left && this.$refs.left.clearCalender()\n\t\t\t\t\t\tthis.$refs.right && this.$refs.right.clearCalender()\n\t\t\t\t\t\tthis.$refs.right && this.$refs.right.next()\n\t\t\t\t\t}\n\t\t\t\t\tif (needEmit) {\n\t\t\t\t\t\tthis.$emit('change', [])\n\t\t\t\t\t\tthis.$emit('input', [])\n\t\t\t\t\t\tthis.$emit('update:modelValue', [])\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tparseDate(date) {\n\t\t\t\tdate = this.fixIosDateFormat(date)\n\t\t\t\tconst defVal = new Date(date)\n\t\t\t\tconst year = defVal.getFullYear()\n\t\t\t\tconst month = defVal.getMonth() + 1\n\t\t\t\tconst day = defVal.getDate()\n\t\t\t\tconst hour = defVal.getHours()\n\t\t\t\tconst minute = defVal.getMinutes()\n\t\t\t\tconst second = defVal.getSeconds()\n\t\t\t\tconst defDate = year + '-' + this.lessTen(month) + '-' + this.lessTen(day)\n\t\t\t\tconst defTime = this.lessTen(hour) + ':' + this.lessTen(minute) + (this.hideSecond ? '' : (':' + this\n\t\t\t\t\t.lessTen(second)))\n\t\t\t\treturn {\n\t\t\t\t\tdefDate,\n\t\t\t\t\tdefTime\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tlessTen(item) {\n\t\t\t\treturn item < 10 ? '0' + item : item\n\t\t\t},\n\n\t\t\t//兼容 iOS、safari 日期格式\n\t\t\tfixIosDateFormat(value) {\n\t\t\t\tif (typeof value === 'string') {\n\t\t\t\t\tvalue = value.replace(/-/g, '/')\n\t\t\t\t}\n\t\t\t\treturn value\n\t\t\t},\n\n\t\t\tleftMonthSwitch(e) {\n\t\t\t\t// console.log('leftMonthSwitch 返回:', e)\n\t\t\t},\n\t\t\trightMonthSwitch(e) {\n\t\t\t\t// console.log('rightMonthSwitch 返回:', e)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\">\n\t$uni-primary: #007aff !default;\n\t\n\t.uni-date {\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: 100%;\n\t\t/* #endif */\n\t\tflex: 1;\n\t}\n\t.uni-date-x {\n\t\tdisplay: flex;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding: 0 10px;\n\t\tborder-radius: 4px;\n\t\tbackground-color: #fff;\n\t\tcolor: #666;\n\t\tfont-size: 14px;\n\t\tflex: 1;\n\t}\n\n\t.uni-date-x--border {\n\t\tbox-sizing: border-box;\n\t\tborder-radius: 4px;\n\t\tborder: 1px solid #e5e5e5;\n\t}\n\n\t.uni-date-editor--x {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tposition: relative;\n\t}\n\n\t.uni-date-editor--x .uni-date__icon-clear {\n\t\tpadding: 0 5px;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\t/* #ifdef H5 */\n\t\tcursor: pointer;\n\t\t/* #endif */\n\t}\n\n\t.uni-date__x-input {\n\t\tpadding: 0 8px;\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: auto;\n\t\t/* #endif */\n\t\tposition: relative;\n\t\toverflow: hidden;\n\t\tflex: 1;\n\t\tline-height: 1;\n\t\tfont-size: 14px;\n\t\theight: 35px;\n\t}\n\n\t.t-c {\n\t\ttext-align: center;\n\t}\n\n\t.uni-date__input {\n\t\theight: 40px;\n\t\twidth: 100%;\n\t\tline-height: 40px;\n\t\tfont-size: 14px;\n\t}\n\n\t.uni-date-range__input {\n\t\ttext-align: center;\n\t\tmax-width: 142px;\n\t}\n\n\t.uni-date-picker__container {\n\t\tposition: relative;\n\t\t/* \t\tposition: fixed;\n\t\tleft: 0;\n\t\tright: 0;\n\t\ttop: 0;\n\t\tbottom: 0;\n\t\tbox-sizing: border-box;\n\t\tz-index: 996;\n\t\tfont-size: 14px; */\n\t}\n\n\t.uni-date-mask {\n\t\tposition: fixed;\n\t\tbottom: 0px;\n\t\ttop: 0px;\n\t\tleft: 0px;\n\t\tright: 0px;\n\t\tbackground-color: rgba(0, 0, 0, 0);\n\t\ttransition-duration: 0.3s;\n\t\tz-index: 996;\n\t}\n\n\t.uni-date-single--x {\n\t\t/* padding: 0 8px; */\n\t\tbackground-color: #fff;\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tz-index: 999;\n\t\tborder: 1px solid #EBEEF5;\n\t\tbox-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n\t\tborder-radius: 4px;\n\t}\n\n\t.uni-date-range--x {\n\t\t/* padding: 0 8px; */\n\t\tbackground-color: #fff;\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tz-index: 999;\n\t\tborder: 1px solid #EBEEF5;\n\t\tbox-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);\n\t\tborder-radius: 4px;\n\t}\n\n\t.uni-date-editor--x__disabled {\n\t\topacity: 0.4;\n\t\tcursor: default;\n\t}\n\n\t.uni-date-editor--logo {\n\t\twidth: 16px;\n\t\theight: 16px;\n\t\tvertical-align: middle;\n\t}\n\n\t/* 添加时间 */\n\t.popup-x-header {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\t/* justify-content: space-between; */\n\t}\n\n\t.popup-x-header--datetime {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tflex: 1;\n\t}\n\n\t.popup-x-body {\n\t\tdisplay: flex;\n\t}\n\n\t.popup-x-footer {\n\t\tpadding: 0 15px;\n\t\tborder-top-color: #F1F1F1;\n\t\tborder-top-style: solid;\n\t\tborder-top-width: 1px;\n\t\t/* background-color: #fff; */\n\t\tline-height: 40px;\n\t\ttext-align: right;\n\t\tcolor: #666;\n\t}\n\n\t.popup-x-footer text:hover {\n\t\tcolor: $uni-primary;\n\t\tcursor: pointer;\n\t\topacity: 0.8;\n\t}\n\n\t.popup-x-footer .confirm {\n\t\tmargin-left: 20px;\n\t\tcolor: $uni-primary;\n\t}\n\n\t.uni-date-changed {\n\t\t/* background-color: #fff; */\n\t\ttext-align: center;\n\t\tcolor: #333;\n\t\tborder-bottom-color: #F1F1F1;\n\t\tborder-bottom-style: solid;\n\t\tborder-bottom-width: 1px;\n\t\t/* padding: 0 50px; */\n\t}\n\n\t.uni-date-changed--time text {\n\t\t/* padding: 0 20px; */\n\t\theight: 50px;\n\t\tline-height: 50px;\n\t}\n\n\t.uni-date-changed .uni-date-changed--time {\n\t\t/* display: flex; */\n\t\tflex: 1;\n\t}\n\n\t.uni-date-changed--time-date {\n\t\tcolor: #333;\n\t\topacity: 0.6;\n\t}\n\n\t.mr-50 {\n\t\tmargin-right: 50px;\n\t}\n\n\t/* picker 弹出层通用的指示小三角, todo：扩展至上下左右方向定位 */\n\t.uni-popper__arrow,\n\t.uni-popper__arrow::after {\n\t\tposition: absolute;\n\t\tdisplay: block;\n\t\twidth: 0;\n\t\theight: 0;\n\t\tborder-color: transparent;\n\t\tborder-style: solid;\n\t\tborder-width: 6px;\n\t}\n\n\t.uni-popper__arrow {\n\t\tfilter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));\n\t\ttop: -6px;\n\t\tleft: 10%;\n\t\tmargin-right: 3px;\n\t\tborder-top-width: 0;\n\t\tborder-bottom-color: #EBEEF5;\n\t}\n\n\t.uni-popper__arrow::after {\n\t\tcontent: \" \";\n\t\ttop: 1px;\n\t\tmargin-left: -6px;\n\t\tborder-top-width: 0;\n\t\tborder-bottom-color: #fff;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js",
    "content": "class Calendar {\n\tconstructor({\n\t\tdate,\n\t\tselected,\n\t\tstartDate,\n\t\tendDate,\n\t\trange,\n\t\t// multipleStatus\n\t} = {}) {\n\t\t// 当前日期\n\t\tthis.date = this.getDate(new Date()) // 当前初入日期\n\t\t// 打点信息\n\t\tthis.selected = selected || [];\n\t\t// 范围开始\n\t\tthis.startDate = startDate\n\t\t// 范围结束\n\t\tthis.endDate = endDate\n\t\tthis.range = range\n\t\t// 多选状态\n\t\tthis.cleanMultipleStatus()\n\t\t// 每周日期\n\t\tthis.weeks = {}\n\t\t// this._getWeek(this.date.fullDate)\n\t\t// this.multipleStatus = multipleStatus\n\t\tthis.lastHover = false\n\t}\n\t/**\n\t * 设置日期\n\t * @param {Object} date\n\t */\n\tsetDate(date) {\n\t\tthis.selectDate = this.getDate(date)\n\t\tthis._getWeek(this.selectDate.fullDate)\n\t}\n\n\t/**\n\t * 清理多选状态\n\t */\n\tcleanMultipleStatus() {\n\t\tthis.multipleStatus = {\n\t\t\tbefore: '',\n\t\t\tafter: '',\n\t\t\tdata: []\n\t\t}\n\t}\n\n\t/**\n\t * 重置开始日期\n\t */\n\tresetSatrtDate(startDate) {\n\t\t// 范围开始\n\t\tthis.startDate = startDate\n\n\t}\n\n\t/**\n\t * 重置结束日期\n\t */\n\tresetEndDate(endDate) {\n\t\t// 范围结束\n\t\tthis.endDate = endDate\n\t}\n\n\t/**\n\t * 获取任意时间\n\t */\n\tgetDate(date, AddDayCount = 0, str = 'day') {\n\t\tif (!date) {\n\t\t\tdate = new Date()\n\t\t}\n\t\tif (typeof date !== 'object') {\n\t\t\tdate = date.replace(/-/g, '/')\n\t\t}\n\t\tconst dd = new Date(date)\n\t\tswitch (str) {\n\t\t\tcase 'day':\n\t\t\t\tdd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期\n\t\t\t\tbreak\n\t\t\tcase 'month':\n\t\t\t\tif (dd.getDate() === 31) {\n\t\t\t\t\tdd.setDate(dd.getDate() + AddDayCount)\n\t\t\t\t} else {\n\t\t\t\t\tdd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase 'year':\n\t\t\t\tdd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期\n\t\t\t\tbreak\n\t\t}\n\t\tconst y = dd.getFullYear()\n\t\tconst m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期，不足10补0\n\t\tconst d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号，不足10补0\n\t\treturn {\n\t\t\tfullDate: y + '-' + m + '-' + d,\n\t\t\tyear: y,\n\t\t\tmonth: m,\n\t\t\tdate: d,\n\t\t\tday: dd.getDay()\n\t\t}\n\t}\n\n\n\t/**\n\t * 获取上月剩余天数\n\t */\n\t_getLastMonthDays(firstDay, full) {\n\t\tlet dateArr = []\n\t\tfor (let i = firstDay; i > 0; i--) {\n\t\t\tconst beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()\n\t\t\tdateArr.push({\n\t\t\t\tdate: beforeDate,\n\t\t\t\tmonth: full.month - 1,\n\t\t\t\tdisable: true\n\t\t\t})\n\t\t}\n\t\treturn dateArr\n\t}\n\t/**\n\t * 获取本月天数\n\t */\n\t_currentMonthDys(dateData, full) {\n\t\tlet dateArr = []\n\t\tlet fullDate = this.date.fullDate\n\t\tfor (let i = 1; i <= dateData; i++) {\n\t\t\tlet isinfo = false\n\t\t\tlet nowDate = full.year + '-' + (full.month < 10 ?\n\t\t\t\tfull.month : full.month) + '-' + (i < 10 ?\n\t\t\t\t'0' + i : i)\n\t\t\t// 是否今天\n\t\t\tlet isDay = fullDate === nowDate\n\t\t\t// 获取打点信息\n\t\t\tlet info = this.selected && this.selected.find((item) => {\n\t\t\t\tif (this.dateEqual(nowDate, item.date)) {\n\t\t\t\t\treturn item\n\t\t\t\t}\n\t\t\t})\n\n\t\t\t// 日期禁用\n\t\t\tlet disableBefore = true\n\t\t\tlet disableAfter = true\n\t\t\tif (this.startDate) {\n\t\t\t\t// let dateCompBefore = this.dateCompare(this.startDate, fullDate)\n\t\t\t\t// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)\n\t\t\t\tdisableBefore = this.dateCompare(this.startDate, nowDate)\n\t\t\t}\n\n\t\t\tif (this.endDate) {\n\t\t\t\t// let dateCompAfter = this.dateCompare(fullDate, this.endDate)\n\t\t\t\t// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)\n\t\t\t\tdisableAfter = this.dateCompare(nowDate, this.endDate)\n\t\t\t}\n\t\t\tlet multiples = this.multipleStatus.data\n\t\t\tlet checked = false\n\t\t\tlet multiplesStatus = -1\n\t\t\tif (this.range) {\n\t\t\t\tif (multiples) {\n\t\t\t\t\tmultiplesStatus = multiples.findIndex((item) => {\n\t\t\t\t\t\treturn this.dateEqual(item, nowDate)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tif (multiplesStatus !== -1) {\n\t\t\t\t\tchecked = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tlet data = {\n\t\t\t\tfullDate: nowDate,\n\t\t\t\tyear: full.year,\n\t\t\t\tdate: i,\n\t\t\t\tmultiple: this.range ? checked : false,\n\t\t\t\tbeforeMultiple: this.isLogicBefore(nowDate, this.multipleStatus.before, this.multipleStatus.after),\n\t\t\t\tafterMultiple: this.isLogicAfter(nowDate, this.multipleStatus.before, this.multipleStatus.after),\n\t\t\t\tmonth: full.month,\n\t\t\t\tdisable: !(disableBefore && disableAfter),\n\t\t\t\tisDay,\n\t\t\t\tuserChecked: false\n\t\t\t}\n\t\t\tif (info) {\n\t\t\t\tdata.extraInfo = info\n\t\t\t}\n\n\t\t\tdateArr.push(data)\n\t\t}\n\t\treturn dateArr\n\t}\n\t/**\n\t * 获取下月天数\n\t */\n\t_getNextMonthDays(surplus, full) {\n\t\tlet dateArr = []\n\t\tfor (let i = 1; i < surplus + 1; i++) {\n\t\t\tdateArr.push({\n\t\t\t\tdate: i,\n\t\t\t\tmonth: Number(full.month) + 1,\n\t\t\t\tdisable: true\n\t\t\t})\n\t\t}\n\t\treturn dateArr\n\t}\n\n\t/**\n\t * 获取当前日期详情\n\t * @param {Object} date\n\t */\n\tgetInfo(date) {\n\t\tif (!date) {\n\t\t\tdate = new Date()\n\t\t}\n\t\tconst dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)\n\t\treturn dateInfo\n\t}\n\n\t/**\n\t * 比较时间大小\n\t */\n\tdateCompare(startDate, endDate) {\n\t\t// 计算截止时间\n\t\tstartDate = new Date(startDate.replace('-', '/').replace('-', '/'))\n\t\t// 计算详细项的截止时间\n\t\tendDate = new Date(endDate.replace('-', '/').replace('-', '/'))\n\t\tif (startDate <= endDate) {\n\t\t\treturn true\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t * 比较时间是否相等\n\t */\n\tdateEqual(before, after) {\n\t\t// 计算截止时间\n\t\tbefore = new Date(before.replace('-', '/').replace('-', '/'))\n\t\t// 计算详细项的截止时间\n\t\tafter = new Date(after.replace('-', '/').replace('-', '/'))\n\t\tif (before.getTime() - after.getTime() === 0) {\n\t\t\treturn true\n\t\t} else {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t/**\n\t *  比较真实起始日期\n\t */\n\n\tisLogicBefore(currentDay, before, after) {\n\t\tlet logicBefore = before\n\t\tif (before && after) {\n\t\t\tlogicBefore = this.dateCompare(before, after) ? before : after\n\t\t}\n\t\treturn this.dateEqual(logicBefore, currentDay)\n\t}\n\n\tisLogicAfter(currentDay, before, after) {\n\t\tlet logicAfter = after\n\t\tif (before && after) {\n\t\t\tlogicAfter = this.dateCompare(before, after) ? after : before\n\t\t}\n\t\treturn this.dateEqual(logicAfter, currentDay)\n\t}\n\n\t/**\n\t * 获取日期范围内所有日期\n\t * @param {Object} begin\n\t * @param {Object} end\n\t */\n\tgeDateAll(begin, end) {\n\t\tvar arr = []\n\t\tvar ab = begin.split('-')\n\t\tvar ae = end.split('-')\n\t\tvar db = new Date()\n\t\tdb.setFullYear(ab[0], ab[1] - 1, ab[2])\n\t\tvar de = new Date()\n\t\tde.setFullYear(ae[0], ae[1] - 1, ae[2])\n\t\tvar unixDb = db.getTime() - 24 * 60 * 60 * 1000\n\t\tvar unixDe = de.getTime() - 24 * 60 * 60 * 1000\n\t\tfor (var k = unixDb; k <= unixDe;) {\n\t\t\tk = k + 24 * 60 * 60 * 1000\n\t\t\tarr.push(this.getDate(new Date(parseInt(k))).fullDate)\n\t\t}\n\t\treturn arr\n\t}\n\n\t/**\n\t *  获取多选状态\n\t */\n\tsetMultiple(fullDate) {\n\t\tlet {\n\t\t\tbefore,\n\t\t\tafter\n\t\t} = this.multipleStatus\n\t\tif (!this.range) return\n\t\tif (before && after) {\n\t\t\tif (!this.lastHover) {\n\t\t\t\tthis.lastHover = true\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.multipleStatus.before = fullDate\n\t\t\tthis.multipleStatus.after = ''\n\t\t\tthis.multipleStatus.data = []\n\t\t\tthis.multipleStatus.fulldate = ''\n\t\t\tthis.lastHover = false\n\t\t} else {\n\t\t\tif (!before) {\n\t\t\t\tthis.multipleStatus.before = fullDate\n\t\t\t\tthis.lastHover = false\n\t\t\t} else {\n\t\t\t\tthis.multipleStatus.after = fullDate\n\t\t\t\tif (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {\n\t\t\t\t\tthis.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus\n\t\t\t\t\t\t.after);\n\t\t\t\t} else {\n\t\t\t\t\tthis.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus\n\t\t\t\t\t\t.before);\n\t\t\t\t}\n\t\t\t\tthis.lastHover = true\n\t\t\t}\n\t\t}\n\t\tthis._getWeek(fullDate)\n\t}\n\n\t/**\n\t *  鼠标 hover 更新多选状态\n\t */\n\tsetHoverMultiple(fullDate) {\n\t\tlet {\n\t\t\tbefore,\n\t\t\tafter\n\t\t} = this.multipleStatus\n\n\t\tif (!this.range) return\n\t\tif (this.lastHover) return\n\n\t\tif (!before) {\n\t\t\tthis.multipleStatus.before = fullDate\n\t\t} else {\n\t\t\tthis.multipleStatus.after = fullDate\n\t\t\tif (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {\n\t\t\t\tthis.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);\n\t\t\t} else {\n\t\t\t\tthis.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);\n\t\t\t}\n\t\t}\n\t\tthis._getWeek(fullDate)\n\t}\n\n\t/**\n\t * 更新默认值多选状态\n\t */\n\tsetDefaultMultiple(before, after) {\n\t\tthis.multipleStatus.before = before\n\t\tthis.multipleStatus.after = after\n\t\tif (before && after) {\n\t\t\tif (this.dateCompare(before, after)) {\n\t\t\t\tthis.multipleStatus.data = this.geDateAll(before, after);\n\t\t\t\tthis._getWeek(after)\n\t\t\t} else {\n\t\t\t\tthis.multipleStatus.data = this.geDateAll(after, before);\n\t\t\t\tthis._getWeek(before)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * 获取每周数据\n\t * @param {Object} dateData\n\t */\n\t_getWeek(dateData) {\n\t\tconst {\n\t\t\tfullDate,\n\t\t\tyear,\n\t\t\tmonth,\n\t\t\tdate,\n\t\t\tday\n\t\t} = this.getDate(dateData)\n\t\tlet firstDay = new Date(year, month - 1, 1).getDay()\n\t\tlet currentDay = new Date(year, month, 0).getDate()\n\t\tlet dates = {\n\t\t\tlastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天\n\t\t\tcurrentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数\n\t\t\tnextMonthDays: [], // 下个月开始几天\n\t\t\tweeks: []\n\t\t}\n\t\tlet canlender = []\n\t\tconst surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)\n\t\tdates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))\n\t\tcanlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)\n\t\tlet weeks = {}\n\t\t// 拼接数组  上个月开始几天 + 本月天数+ 下个月开始几天\n\t\tfor (let i = 0; i < canlender.length; i++) {\n\t\t\tif (i % 7 === 0) {\n\t\t\t\tweeks[parseInt(i / 7)] = new Array(7)\n\t\t\t}\n\t\t\tweeks[parseInt(i / 7)][i % 7] = canlender[i]\n\t\t}\n\t\tthis.canlender = canlender\n\t\tthis.weeks = weeks\n\t}\n\n\t//静态方法\n\t// static init(date) {\n\t// \tif (!this.instance) {\n\t// \t\tthis.instance = new Calendar(date);\n\t// \t}\n\t// \treturn this.instance;\n\t// }\n}\n\n\nexport default Calendar\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/package.json",
    "content": "{\n  \"id\": \"uni-datetime-picker\",\n  \"displayName\": \"uni-datetime-picker 日期选择器\",\n  \"version\": \"2.2.10\",\n  \"description\": \"uni-datetime-picker 日期时间选择器，支持日历，支持范围选择\",\n  \"keywords\": [\n    \"uni-datetime-picker\",\n    \"uni-ui\",\n    \"uniui\",\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    \"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    \"type\": \"component-vue\"\n  },\n  \"uni_modules\": {\n    \"dependencies\": [\n\t\t\t\"uni-scss\",\n\t\t\t\"uni-icons\"\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\": \"n\"\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": "bookkeeping_user_uniapp/uni_modules/uni-datetime-picker/readme.md",
    "content": "\n\n> `重要通知：组件升级更新 2.0.0 后，支持日期+时间范围选择，组件 ui 将使用日历选择日期，ui 变化较大，同时支持 PC 和 移动端。此版本不向后兼容，不再支持单独的时间选择（type=time）及相关的 hide-second 属性（时间选可使用内置组件 picker）。若仍需使用旧版本，可在插件市场下载*非uni_modules版本*，旧版本将不再维护`\n\n## DatetimePicker 时间选择器\n\n> **组件名：uni-datetime-picker**\n> 代码块： `uDatetimePicker`\n\n\n该组件的优势是，支持**时间戳**输入和输出（起始时间、终止时间也支持时间戳），可**同时选择**日期和时间。\n\n若只是需要单独选择日期和时间，不需要时间戳输入和输出，可使用原生的 picker 组件。\n\n**_点击 picker 默认值规则：_**\n\n- 若设置初始值 value, 会显示在 picker 显示框中\n- 若无初始值 value，则初始值 value 为当前本地时间 Date.now()， 但不会显示在 picker 显示框中\n\n### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)\n#### 如使用过程中有任何问题，或者您对uni-ui有一些好的建议，欢迎加入 uni-ui 交流群：871950839 "
  },
  {
    "path": "bookkeeping_user_uniapp/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": "bookkeeping_user_uniapp/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": "bookkeeping_user_uniapp/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 -->\n</template>\n\n<script>\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} \n\t// #ifdef APP-NVUE\n\tvar domModule = weex.requireModule('dom');\n\timport iconUrl from './uniicons.ttf'\n\tdomModule.addRule('fontFace', {\n\t\t'fontFamily': \"uniicons\",\n\t\t'src': \"url('\"+iconUrl+\"')\"\n\t});\n\t// #endif\n\n\t/**\n\t * Icons 图标\n\t * @description 用于展示 icons 图标\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=28\n\t * @property {Number} size 图标大小\n\t * @property {String} type 图标图案，参考示例\n\t * @property {String} color 图标颜色\n\t * @property {String} customPrefix 自定义图标\n\t * @event {Function} click 点击 Icon 触发事件\n\t */\n\texport default {\n\t\tname: 'UniIcons',\n\t\temits:['click'],\n\t\tprops: {\n\t\t\ttype: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tcolor: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: '#333333'\n\t\t\t},\n\t\t\tsize: {\n\t\t\t\ttype: [Number, String],\n\t\t\t\tdefault: 16\n\t\t\t},\n\t\t\tcustomPrefix:{\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t}\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\ticons: icons.glyphs\n\t\t\t}\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},\n\t\tmethods: {\n\t\t\t_onClick() {\n\t\t\t\tthis.$emit('click')\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\">\n\t/* #ifndef APP-NVUE */\n\t@import './uniicons.css';\n\t@font-face {\n\t\tfont-family: uniicons;\n\t\tsrc: url('./uniicons.ttf') format('truetype');\n\t}\n\n\t/* #endif */\n\t.uni-icons {\n\t\tfont-family: uniicons;\n\t\ttext-decoration: none;\n\t\ttext-align: center;\n\t}\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/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": "bookkeeping_user_uniapp/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": "bookkeeping_user_uniapp/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": "bookkeeping_user_uniapp/uni_modules/uni-list/changelog.md",
    "content": "## 1.2.1（2022-03-30）\n- 删除无用文件\n## 1.2.0（2021-11-23）\n- 优化 组件UI，并提供设计资源，详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)\n- 文档迁移，详见:[https://uniapp.dcloud.io/component/uniui/uni-list](https://uniapp.dcloud.io/component/uniui/uni-list)\n## 1.1.3（2021-08-30）\n- 修复 在vue3中to属性在发行应用的时候报错的bug\n## 1.1.2（2021-07-30）\n- 优化 vue3下事件警告的问题\n## 1.1.1（2021-07-21）\n- 修复 与其他组件嵌套使用时，点击失效的Bug\n## 1.1.0（2021-07-13）\n- 组件兼容 vue3，如何创建vue3项目，详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)\n## 1.0.17（2021-05-12）\n- 新增 组件示例地址\n## 1.0.16（2021-02-05）\n- 优化 组件引用关系，通过uni_modules引用组件\n## 1.0.15（2021-02-05）\n- 调整为uni_modules目录规范\n- 修复 uni-list-chat 角标显示不正常的问题\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-list/components/uni-list/uni-list.vue",
    "content": "<template>\n\t<!-- #ifndef APP-NVUE -->\n\t<view class=\"uni-list uni-border-top-bottom\">\n\t\t<view v-if=\"border\" class=\"uni-list--border-top\"></view>\n\t\t<slot />\n\t\t<view v-if=\"border\" class=\"uni-list--border-bottom\"></view>\n\t</view>\n\t<!-- #endif -->\n\t<!-- #ifdef APP-NVUE -->\n\t<list class=\"uni-list\" :class=\"{ 'uni-list--border': border }\" :enableBackToTop=\"enableBackToTop\" loadmoreoffset=\"15\"><slot /></list>\n\t<!-- #endif -->\n</template>\n\n<script>\n/**\n * List 列表\n * @description 列表组件\n * @tutorial https://ext.dcloud.net.cn/plugin?id=24\n * @property {String} \tborder = [true|false] \t\t标题\n */\nexport default {\n\tname: 'uniList',\n\t'mp-weixin': {\n\t\toptions: {\n\t\t\tmultipleSlots: false\n\t\t}\n\t},\n\tprops: {\n\t\tenableBackToTop: {\n\t\t\ttype: [Boolean, String],\n\t\t\tdefault: false\n\t\t},\n\t\tscrollY: {\n\t\t\ttype: [Boolean, String],\n\t\t\tdefault: false\n\t\t},\n\t\tborder: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true\n\t\t}\n\t},\n\t// provide() {\n\t// \treturn {\n\t// \t\tlist: this\n\t// \t};\n\t// },\n\tcreated() {\n\t\tthis.firstChildAppend = false;\n\t},\n\tmethods: {\n\t\tloadMore(e) {\n\t\t\tthis.$emit('scrolltolower');\n\t\t}\n\t}\n};\n</script>\n<style lang=\"scss\" >\n$uni-bg-color:#ffffff;\n$uni-border-color:#e5e5e5;\n.uni-list {\n\t/* #ifndef APP-NVUE */\n\tdisplay: flex;\n\t/* #endif */\n\tbackground-color: $uni-bg-color;\n\tposition: relative;\n\tflex-direction: column;\n}\n\n.uni-list--border {\n\tposition: relative;\n\t/* #ifdef APP-NVUE */\n\tborder-top-color: $uni-border-color;\n\tborder-top-style: solid;\n\tborder-top-width: 0.5px;\n\tborder-bottom-color: $uni-border-color;\n\tborder-bottom-style: solid;\n\tborder-bottom-width: 0.5px;\n\t/* #endif */\n\tz-index: -1;\n}\n\n/* #ifndef APP-NVUE */\n\n.uni-list--border-top {\n\tposition: absolute;\n\ttop: 0;\n\tright: 0;\n\tleft: 0;\n\theight: 1px;\n\t-webkit-transform: scaleY(0.5);\n\ttransform: scaleY(0.5);\n\tbackground-color: $uni-border-color;\n\tz-index: 1;\n}\n\n.uni-list--border-bottom {\n\tposition: absolute;\n\tbottom: 0;\n\tright: 0;\n\tleft: 0;\n\theight: 1px;\n\t-webkit-transform: scaleY(0.5);\n\ttransform: scaleY(0.5);\n\tbackground-color: $uni-border-color;\n}\n\n/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.vue",
    "content": "<template>\n    <!-- #ifdef APP-NVUE -->\n    <refresh :display=\"display\" @refresh=\"onrefresh\" @pullingdown=\"onpullingdown\">\n        <slot />\n    </refresh>\n    <!-- #endif -->\n    <!-- #ifndef APP-NVUE -->\n    <view ref=\"uni-refresh\" class=\"uni-refresh\" v-show=\"isShow\">\n        <slot />\n    </view>\n    <!-- #endif -->\n</template>\n\n<script>\n    export default {\n        name: 'UniRefresh',\n        props: {\n            display: {\n                type: [String],\n                default: \"hide\"\n            }\n        },\n        data() {\n            return {\n                pulling: false\n            }\n        },\n        computed: {\n            isShow() {\n                if (this.display === \"show\" || this.pulling === true) {\n                    return true;\n                }\n                return false;\n            }\n        },\n        created() {},\n        methods: {\n            onchange(value) {\n                this.pulling = value;\n            },\n            onrefresh(e) {\n                this.$emit(\"refresh\", e);\n            },\n            onpullingdown(e) {\n                // #ifdef APP-NVUE\n                this.$emit(\"pullingdown\", e);\n                // #endif\n                // #ifndef APP-NVUE\n                var detail = {\n                    viewHeight: 90,\n                    pullingDistance: e.height\n                }\n                this.$emit(\"pullingdown\", detail);\n                // #endif\n            }\n        }\n    }\n</script>\n\n<style>\n    .uni-refresh {\n        height: 0;\n        overflow: hidden;\n    }\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.wxs",
    "content": "var pullDown = {\n    threshold: 95,\n    maxHeight: 200,\n    callRefresh: 'onrefresh',\n    callPullingDown: 'onpullingdown',\n    refreshSelector: '.uni-refresh'\n};\n\nfunction ready(newValue, oldValue, ownerInstance, instance) {\n    var state = instance.getState()\n    state.canPullDown = newValue;\n    // console.log(newValue);\n}\n\nfunction touchStart(e, instance) {\n    var state = instance.getState();\n    state.refreshInstance = instance.selectComponent(pullDown.refreshSelector);\n    state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined);\n    if (!state.canPullDown) {\n        return\n    }\n\n    // console.log(\"touchStart\");\n\n    state.height = 0;\n    state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;\n    state.refreshInstance.setStyle({\n        'height': 0\n    });\n    state.refreshInstance.callMethod(\"onchange\", true);\n}\n\nfunction touchMove(e, ownerInstance) {\n    var instance = e.instance;\n    var state = instance.getState();\n    if (!state.canPullDown) {\n        return\n    }\n\n    var oldHeight = state.height;\n    var endY = e.touches[0].pageY || e.changedTouches[0].pageY;\n    var height = endY - state.touchStartY;\n    if (height > pullDown.maxHeight) {\n        return;\n    }\n\n    var refreshInstance = state.refreshInstance;\n    refreshInstance.setStyle({\n        'height': height + 'px'\n    });\n\n    height = height < pullDown.maxHeight ? height : pullDown.maxHeight;\n    state.height = height;\n    refreshInstance.callMethod(pullDown.callPullingDown, {\n        height: height\n    });\n}\n\nfunction touchEnd(e, ownerInstance) {\n    var state = e.instance.getState();\n    if (!state.canPullDown) {\n        return\n    }\n\n    state.refreshInstance.callMethod(\"onchange\", false);\n\n    var refreshInstance = state.refreshInstance;\n    if (state.height > pullDown.threshold) {\n        refreshInstance.callMethod(pullDown.callRefresh);\n        return;\n    }\n\n    refreshInstance.setStyle({\n        'height': 0\n    });\n}\n\nfunction propObserver(newValue, oldValue, instance) {\n    pullDown = newValue;\n}\n\nmodule.exports = {\n    touchmove: touchMove,\n    touchstart: touchStart,\n    touchend: touchEnd,\n    propObserver: propObserver\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue",
    "content": "<template>\n\t<!-- #ifdef APP-NVUE -->\n\t<cell>\n\t\t<!-- #endif -->\n\t\t<view class=\"uni-list-ad\">\n\t\t\t<view v-if=\"borderShow\" :class=\"{'uni-list--border':border,'uni-list-item--first':isFirstChild}\"></view>\n\t\t\t<ad style=\"width: 200px;height: 300px;border-width: 1px;border-color: red;border-style: solid;\" adpid=\"1111111111\"\n\t\t\t unit-id=\"\" appid=\"\" apid=\"\" type=\"feed\" @error=\"aderror\" @close=\"closeAd\"></ad>\n\t\t</view>\n\t\t<!-- #ifdef APP-NVUE -->\n\t</cell>\n\t<!-- #endif -->\n\n</template>\n\n<script>\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom');\n\t// #endif\n\texport default {\n\t\tname: 'UniListAd',\n\t\tprops: {\n\t\t\ttitle: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: '',\n\n\t\t\t}\n\t\t},\n\t\t// inject: ['list'],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisFirstChild: false,\n\t\t\t\tborder: false,\n\t\t\t\tborderShow: true,\n\t\t\t}\n\t\t},\n\n\t\tmounted() {\n\t\t\tthis.list = this.getForm()\n\t\t\tif (this.list) {\n\t\t\t\tif (!this.list.firstChildAppend) {\n\t\t\t\t\tthis.list.firstChildAppend = true\n\t\t\t\t\tthis.isFirstChild = true\n\t\t\t\t}\n\t\t\t\tthis.border = this.list.border\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t/**\n\t\t\t * 获取父元素实例\n\t\t\t */\n\t\t\tgetForm(name = 'uniList') {\n\t\t\t\tlet parent = this.$parent;\n\t\t\t\tlet parentName = parent.$options.name;\n\t\t\t\twhile (parentName !== name) {\n\t\t\t\t\tparent = parent.$parent;\n\t\t\t\t\tif (!parent) return false\n\t\t\t\t\tparentName = parent.$options.name;\n\t\t\t\t}\n\t\t\t\treturn parent;\n\t\t\t},\n\t\t\taderror(e) {\n\t\t\t\tconsole.log(\"aderror: \" + JSON.stringify(e.detail));\n\t\t\t},\n\t\t\tcloseAd(e) {\n\t\t\t\tthis.borderShow = false\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" >\n\t.uni-list-ad {\n\t\tposition: relative;\n\t\tborder: 1px red solid;\n\t}\n\n\t.uni-list--border {\n\t\tposition: relative;\n\t\tpadding-bottom: 1px;\n\t\t/* #ifdef APP-PLUS */\n\t\tborder-top-color: $uni-border-color;\n\t\tborder-top-style: solid;\n\t\tborder-top-width: 0.5px;\n\t\t/* #endif */\n\t\tmargin-left: $uni-spacing-row-lg;\n\t}\n\n\t/* #ifndef APP-NVUE */\n\t.uni-list--border:after {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tleft: 0;\n\t\theight: 1px;\n\t\tcontent: '';\n\t\t-webkit-transform: scaleY(.5);\n\t\ttransform: scaleY(.5);\n\t\tbackground-color: $uni-border-color;\n\t}\n\n\t.uni-list-item--first:after {\n\t\theight: 0px;\n\t}\n\n\t/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss",
    "content": "/**\n * 这里是 uni-list 组件内置的常用样式变量\n * 如果需要覆盖样式，这里提供了基本的组件样式变量，您可以尝试修改这里的变量，去完成样式替换，而不用去修改源码\n *\n */\n\n// 背景色\n$background-color : #fff;\n// 分割线颜色\n$divide-line-color : #e5e5e5;\n\n// 默认头像大小，如需要修改此值，注意同步修改 js 中的值 const avatarWidth = xx ，目前只支持方形头像\n// nvue 页面不支持修改头像大小\n$avatar-width : 45px ;\n\n// 头像边框\n$avatar-border-radius: 5px;\n$avatar-border-color: #eee;\n$avatar-border-width: 1px;\n\n// 标题文字样式\n$title-size : 16px;\n$title-color : #3b4144;\n$title-weight : normal;\n\n// 描述文字样式\n$note-size : 12px;\n$note-color : #999;\n$note-weight : normal;\n\n// 右侧额外内容默认样式\n$right-text-size : 12px;\n$right-text-color : #999;\n$right-text-weight : normal;\n\n// 角标样式\n// nvue 页面不支持修改圆点位置以及大小\n// 角标在左侧时，角标的位置，默认为 0 ，负数左/下移动，正数右/上移动\n$badge-left: 0px;\n$badge-top: 0px;\n\n// 显示圆点时，圆点大小\n$dot-width: 10px;\n$dot-height: 10px;\n\n// 显示角标时，角标大小和字体大小\n$badge-size : 18px;\n$badge-font : 12px;\n// 显示角标时，角标前景色\n$badge-color : #fff;\n// 显示角标时，角标背景色\n$badge-background-color : #ff5a5f;\n// 显示角标时，角标左右间距\n$badge-space : 6px;\n\n// 状态样式\n// 选中颜色\n$hover : #f5f5f5;\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue",
    "content": "<template>\n\t<!-- #ifdef APP-NVUE -->\n\t<cell>\n\t\t<!-- #endif -->\n\t\t<view :hover-class=\"!clickable && !link ? '' : 'uni-list-chat--hover'\" class=\"uni-list-chat\" @click.stop=\"onClick\">\n\t\t\t<view :class=\"{ 'uni-list--border': border, 'uni-list-chat--first': isFirstChild }\"></view>\n\t\t\t<view class=\"uni-list-chat__container\">\n\t\t\t\t<view class=\"uni-list-chat__header-warp\">\n\t\t\t\t\t<view v-if=\"avatarCircle || avatarList.length === 0\" class=\"uni-list-chat__header\" :class=\"{ 'header--circle': avatarCircle }\">\n\t\t\t\t\t\t<image class=\"uni-list-chat__header-image\" :class=\"{ 'header--circle': avatarCircle }\" :src=\"avatar\" mode=\"aspectFill\"></image>\n\t\t\t\t\t</view>\n\t\t\t\t\t<!-- 头像组 -->\n\t\t\t\t\t<view v-else class=\"uni-list-chat__header\">\n\t\t\t\t\t\t<view v-for=\"(item, index) in avatarList\" :key=\"index\" class=\"uni-list-chat__header-box\" :class=\"computedAvatar\"\n\t\t\t\t\t\t :style=\"{ width: imageWidth + 'px', height: imageWidth + 'px' }\">\n\t\t\t\t\t\t\t<image class=\"uni-list-chat__header-image\" :style=\"{ width: imageWidth + 'px', height: imageWidth + 'px' }\" :src=\"item.url\"\n\t\t\t\t\t\t\t mode=\"aspectFill\"></image>\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 v-if=\"badgeText && badgePositon === 'left'\" class=\"uni-list-chat__badge uni-list-chat__badge-pos\" :class=\"[isSingle]\">\n\t\t\t\t\t<text class=\"uni-list-chat__badge-text\">{{ badgeText === 'dot' ? '' : badgeText }}</text>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"uni-list-chat__content\">\n\t\t\t\t\t<view class=\"uni-list-chat__content-main\">\n\t\t\t\t\t\t<text class=\"uni-list-chat__content-title uni-ellipsis\">{{ title }}</text>\n\t\t\t\t\t\t<text class=\"uni-list-chat__content-note uni-ellipsis\">{{ note }}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"uni-list-chat__content-extra\">\n\t\t\t\t\t\t<slot>\n\t\t\t\t\t\t\t<text class=\"uni-list-chat__content-extra-text\">{{ time }}</text>\n\t\t\t\t\t\t\t<view v-if=\"badgeText && badgePositon === 'right'\" class=\"uni-list-chat__badge\" :class=\"[isSingle, badgePositon === 'right' ? 'uni-list-chat--right' : '']\">\n\t\t\t\t\t\t\t\t<text class=\"uni-list-chat__badge-text\">{{ badgeText === 'dot' ? '' : badgeText }}</text>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</slot>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t\t<!-- #ifdef APP-NVUE -->\n\t</cell>\n\t<!-- #endif -->\n</template>\n\n<script>\n\t// 头像大小\n\tconst avatarWidth = 45;\n\n\t/**\n\t * ListChat 聊天列表\n\t * @description 聊天列表,用于创建聊天类列表\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=24\n\t * @property {String} \ttitle \t\t\t\t\t\t\t标题\n\t * @property {String} \tnote \t\t\t\t\t\t\t描述\n\t * @property {Boolean} \tclickable = [true|false] \t\t是否开启点击反馈，默认为false\n\t * @property {String} \tbadgeText\t\t\t\t\t\t数字角标内容\n\t * @property {String}  \tbadgePositon = [left|right]\t\t角标位置，默认为 right\n\t * @property {String} \tlink = [false｜navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈，默认为false\n\t *  @value false\t \t不开启\n\t *  @value navigateTo \t同 uni.navigateTo()\n\t * \t@value redirectTo \t同 uni.redirectTo()\n\t * \t@value reLaunch   \t同 uni.reLaunch()\n\t * \t@value switchTab  \t同 uni.switchTab()\n\t * @property {String | PageURIString} \tto  \t\t\t跳转目标页面\n\t * @property {String} \ttime\t\t\t\t\t\t\t右侧时间显示\n\t * @property {Boolean} \tavatarCircle = [true|false]\t\t是否显示圆形头像，默认为false\n\t * @property {String} \tavatar\t\t\t\t\t\t\t头像地址，avatarCircle 不填时生效\n\t * @property {Array} \tavatarList \t\t\t\t\t\t头像组，格式为 [{url:''}]\n\t * @event {Function} \tclick \t\t\t\t\t\t\t点击 uniListChat 触发事件\n\t */\n\texport default {\n\t\tname: 'UniListChat',\n\t\temits:['click'],\n\t\tprops: {\n\t\t\ttitle: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tnote: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tclickable: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tlink: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tto: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tbadgeText: {\n\t\t\t\ttype: [String, Number],\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tbadgePositon: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'right'\n\t\t\t},\n\t\t\ttime: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tavatarCircle: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tavatar: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tavatarList: {\n\t\t\t\ttype: Array,\n\t\t\t\tdefault () {\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// inject: ['list'],\n\t\tcomputed: {\n\t\t\tisSingle() {\n\t\t\t\tif (this.badgeText === 'dot') {\n\t\t\t\t\treturn 'uni-badge--dot';\n\t\t\t\t} else {\n\t\t\t\t\tconst badgeText = this.badgeText.toString();\n\t\t\t\t\tif (badgeText.length > 1) {\n\t\t\t\t\t\treturn 'uni-badge--complex';\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn 'uni-badge--single';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tcomputedAvatar() {\n\t\t\t\tif (this.avatarList.length > 4) {\n\t\t\t\t\tthis.imageWidth = avatarWidth * 0.31;\n\t\t\t\t\treturn 'avatarItem--3';\n\t\t\t\t} else if (this.avatarList.length > 1) {\n\t\t\t\t\tthis.imageWidth = avatarWidth * 0.47;\n\t\t\t\t\treturn 'avatarItem--2';\n\t\t\t\t} else {\n\t\t\t\t\tthis.imageWidth = avatarWidth;\n\t\t\t\t\treturn 'avatarItem--1';\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisFirstChild: false,\n\t\t\t\tborder: true,\n\t\t\t\t// avatarList: 3,\n\t\t\t\timageWidth: 50\n\t\t\t};\n\t\t},\n\t\tmounted() {\n\t\t\tthis.list = this.getForm()\n\t\t\tif (this.list) {\n\t\t\t\tif (!this.list.firstChildAppend) {\n\t\t\t\t\tthis.list.firstChildAppend = true;\n\t\t\t\t\tthis.isFirstChild = true;\n\t\t\t\t}\n\t\t\t\tthis.border = this.list.border;\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t/**\n\t\t\t * 获取父元素实例\n\t\t\t */\n\t\t\tgetForm(name = 'uniList') {\n\t\t\t\tlet parent = this.$parent;\n\t\t\t\tlet parentName = parent.$options.name;\n\t\t\t\twhile (parentName !== name) {\n\t\t\t\t\tparent = parent.$parent;\n\t\t\t\t\tif (!parent) return false\n\t\t\t\t\tparentName = parent.$options.name;\n\t\t\t\t}\n\t\t\t\treturn parent;\n\t\t\t},\n\t\t\tonClick() {\n\t\t\t\tif (this.to !== '') {\n\t\t\t\t\tthis.openPage();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (this.clickable || this.link) {\n\t\t\t\t\tthis.$emit('click', {\n\t\t\t\t\t\tdata: {}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\topenPage() {\n\t\t\t\tif (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {\n\t\t\t\t\tthis.pageApi(this.link);\n\t\t\t\t} else {\n\t\t\t\t\tthis.pageApi('navigateTo');\n\t\t\t\t}\n\t\t\t},\n\t\t\tpageApi(api) {\n\t\t\t\tuni[api]({\n\t\t\t\t\turl: this.to,\n\t\t\t\t\tsuccess: res => {\n\t\t\t\t\t\tthis.$emit('click', {\n\t\t\t\t\t\t\tdata: res\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\tfail: err => {\n\t\t\t\t\t\tthis.$emit('click', {\n\t\t\t\t\t\t\tdata: err\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconsole.error(err.errMsg);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" >\n\t$uni-font-size-lg:16px;\n\t$uni-spacing-row-sm: 5px;\n\t$uni-spacing-row-base: 10px;\n\t$uni-spacing-row-lg: 15px;\n\t$background-color: #fff;\n\t$divide-line-color: #e5e5e5;\n\t$avatar-width: 45px;\n\t$avatar-border-radius: 5px;\n\t$avatar-border-color: #eee;\n\t$avatar-border-width: 1px;\n\t$title-size: 16px;\n\t$title-color: #3b4144;\n\t$title-weight: normal;\n\t$note-size: 12px;\n\t$note-color: #999;\n\t$note-weight: normal;\n\t$right-text-size: 12px;\n\t$right-text-color: #999;\n\t$right-text-weight: normal;\n\t$badge-left: 0px;\n\t$badge-top: 0px;\n\t$dot-width: 10px;\n\t$dot-height: 10px;\n\t$badge-size: 18px;\n\t$badge-font: 12px;\n\t$badge-color: #fff;\n\t$badge-background-color: #ff5a5f;\n\t$badge-space: 6px;\n\t$hover: #f5f5f5;\n\n\t.uni-list-chat {\n\t\tfont-size: $uni-font-size-lg;\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\tjustify-content: space-between;\n\t\tbackground-color: $background-color;\n\t}\n\n\t// .uni-list-chat--disabled {\n\t// \topacity: 0.3;\n\t// }\n\n\t.uni-list-chat--hover {\n\t\tbackground-color: $hover;\n\t}\n\n\t.uni-list--border {\n\t\tposition: relative;\n\t\tmargin-left: $uni-spacing-row-lg;\n\t\t/* #ifdef APP-PLUS */\n\t\tborder-top-color: $divide-line-color;\n\t\tborder-top-style: solid;\n\t\tborder-top-width: 0.5px;\n\t\t/* #endif */\n\t}\n\n\t/* #ifndef APP-NVUE */\n\t.uni-list--border:after {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tleft: 0;\n\t\theight: 1px;\n\t\tcontent: '';\n\t\t-webkit-transform: scaleY(0.5);\n\t\ttransform: scaleY(0.5);\n\t\tbackground-color: $divide-line-color;\n\t}\n\n\t.uni-list-item--first:after {\n\t\theight: 0px;\n\t}\n\n\t/* #endif */\n\n\t.uni-list-chat--first {\n\t\tborder-top-width: 0px;\n\t}\n\n\t.uni-ellipsis {\n\t\t/* #ifndef APP-NVUE */\n\t\toverflow: hidden;\n\t\twhite-space: nowrap;\n\t\ttext-overflow: ellipsis;\n\t\t/* #endif */\n\t\t/* #ifdef APP-NVUE */\n\t\tlines: 1;\n\t\t/* #endif */\n\t}\n\n\t.uni-ellipsis-2 {\n\t\t/* #ifndef APP-NVUE */\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\tdisplay: -webkit-box;\n\t\t-webkit-line-clamp: 2;\n\t\t-webkit-box-orient: vertical;\n\t\t/* #endif */\n\n\t\t/* #ifdef APP-NVUE */\n\t\tlines: 2;\n\t\t/* #endif */\n\t}\n\n\t.uni-list-chat__container {\n\t\tposition: relative;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tflex: 1;\n\t\tpadding: $uni-spacing-row-base $uni-spacing-row-lg;\n\t\tposition: relative;\n\t\toverflow: hidden;\n\t}\n\n\t.uni-list-chat__header-warp {\n\t\tposition: relative;\n\t}\n\n\t.uni-list-chat__header {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\talign-content: center;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tflex-wrap: wrap-reverse;\n\t\t/* #ifdef APP-NVUE */\n\t\twidth: 50px;\n\t\theight: 50px;\n\t\t/* #endif */\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: $avatar-width;\n\t\theight: $avatar-width;\n\t\t/* #endif */\n\n\t\tborder-radius: $avatar-border-radius;\n\t\tborder-color: $avatar-border-color;\n\t\tborder-width: $avatar-border-width;\n\t\tborder-style: solid;\n\t\toverflow: hidden;\n\t}\n\n\t.uni-list-chat__header-box {\n\t\t/* #ifndef APP-PLUS */\n\t\tbox-sizing: border-box;\n\t\tdisplay: flex;\n\t\twidth: $avatar-width;\n\t\theight: $avatar-width;\n\t\t/* #endif */\n\t\t/* #ifdef APP-NVUE */\n\t\twidth: 50px;\n\t\theight: 50px;\n\t\t/* #endif */\n\t\toverflow: hidden;\n\t\tborder-radius: 2px;\n\t}\n\n\t.uni-list-chat__header-image {\n\t\tmargin: 1px;\n\t\t/* #ifdef APP-NVUE */\n\t\twidth: 50px;\n\t\theight: 50px;\n\t\t/* #endif */\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: $avatar-width;\n\t\theight: $avatar-width;\n\t\t/* #endif */\n\t}\n\n\t/* #ifndef APP-NVUE */\n\t.uni-list-chat__header-image {\n\t\tdisplay: block;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.avatarItem--1 {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t}\n\n\t.avatarItem--2 {\n\t\twidth: 47%;\n\t\theight: 47%;\n\t}\n\n\t.avatarItem--3 {\n\t\twidth: 32%;\n\t\theight: 32%;\n\t}\n\n\t/* #endif */\n\t.header--circle {\n\t\tborder-radius: 50%;\n\t}\n\n\t.uni-list-chat__content {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tflex: 1;\n\t\toverflow: hidden;\n\t\tpadding: 2px 0;\n\t}\n\n\t.uni-list-chat__content-main {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: column;\n\t\tjustify-content: space-between;\n\t\tpadding-left: $uni-spacing-row-base;\n\t\tflex: 1;\n\t\toverflow: hidden;\n\t}\n\n\t.uni-list-chat__content-title {\n\t\tfont-size: $title-size;\n\t\tcolor: $title-color;\n\t\tfont-weight: $title-weight;\n\t\toverflow: hidden;\n\t}\n\n\t.uni-list-chat__content-note {\n\t\tmargin-top: 3px;\n\t\tcolor: $note-color;\n\t\tfont-size: $note-size;\n\t\tfont-weight: $title-weight;\n\t\toverflow: hidden;\n\t}\n\n\t.uni-list-chat__content-extra {\n\t\t/* #ifndef APP-NVUE */\n\t\tflex-shrink: 0;\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: column;\n\t\tjustify-content: space-between;\n\t\talign-items: flex-end;\n\t\tmargin-left: 5px;\n\t}\n\n\t.uni-list-chat__content-extra-text {\n\t\tcolor: $right-text-color;\n\t\tfont-size: $right-text-size;\n\t\tfont-weight: $right-text-weight;\n\t\toverflow: hidden;\n\t}\n\n\t.uni-list-chat__badge-pos {\n\t\tposition: absolute;\n\t\t/* #ifdef APP-NVUE */\n\t\tleft: 55px;\n\t\ttop: 3px;\n\t\t/* #endif */\n\t\t/* #ifndef APP-NVUE */\n\t\tleft: calc(#{$avatar-width} + 10px - #{$badge-space} + #{$badge-left});\n\t\ttop: calc(#{$uni-spacing-row-base}/ 2 + 1px + #{$badge-top});\n\t\t/* #endif */\n\t}\n\n\t.uni-list-chat__badge {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tborder-radius: 100px;\n\t\tbackground-color: $badge-background-color;\n\t}\n\n\t.uni-list-chat__badge-text {\n\t\tcolor: $badge-color;\n\t\tfont-size: $badge-font;\n\t}\n\n\t.uni-badge--single {\n\t\t/* #ifndef APP-NVUE */\n\t\t// left: calc(#{$avatar-width} + 7px + #{$badge-left});\n\t\t/* #endif */\n\t\twidth: $badge-size;\n\t\theight: $badge-size;\n\t}\n\n\t.uni-badge--complex {\n\t\t/* #ifdef APP-NVUE */\n\t\tleft: 50px;\n\t\t/* #endif */\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: auto;\n\t\t/* #endif */\n\t\theight: $badge-size;\n\t\tpadding: 0 $badge-space;\n\t}\n\n\t.uni-badge--dot {\n\t\t/* #ifdef APP-NVUE */\n\t\tleft: 60px;\n\t\ttop: 6px;\n\t\t/* #endif */\n\t\t/* #ifndef APP-NVUE */\n\t\tleft: calc(#{$avatar-width} + 15px - #{$dot-width}/ 2 + 1px + #{$badge-left});\n\t\t/* #endif */\n\t\twidth: $dot-width;\n\t\theight: $dot-height;\n\t\tpadding: 0;\n\t}\n\n\t.uni-list-chat--right {\n\t\t/* #ifdef APP-NVUE */\n\t\tleft: 0;\n\t\t/* #endif */\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue",
    "content": "<template>\n\t<!-- #ifdef APP-NVUE -->\n\t<cell>\n\t\t<!-- #endif -->\n\n\t\t<view :class=\"{ 'uni-list-item--disabled': disabled }\"\n\t\t\t:hover-class=\"(!clickable && !link) || disabled || showSwitch ? '' : 'uni-list-item--hover'\"\n\t\t\tclass=\"uni-list-item\" @click=\"onClick\">\n\t\t\t<view v-if=\"!isFirstChild\" class=\"border--left\" :class=\"{ 'uni-list--border': border }\"></view>\n\t\t\t<view class=\"uni-list-item__container\"\n\t\t\t\t:class=\"{ 'container--right': showArrow || link, 'flex--direction': direction === 'column' }\">\n\t\t\t\t<slot name=\"header\">\n\t\t\t\t\t<view class=\"uni-list-item__header\">\n\t\t\t\t\t\t<view v-if=\"thumb\" class=\"uni-list-item__icon\">\n\t\t\t\t\t\t\t<image :src=\"thumb\" class=\"uni-list-item__icon-img\" :class=\"['uni-list--' + thumbSize]\" />\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view v-else-if=\"showExtraIcon\" class=\"uni-list-item__icon\">\n\t\t\t\t\t\t\t<uni-icons :color=\"extraIcon.color\" :size=\"extraIcon.size\" :type=\"extraIcon.type\" />\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</slot>\n\t\t\t\t<slot name=\"body\">\n\t\t\t\t\t<view class=\"uni-list-item__content\"\n\t\t\t\t\t\t:class=\"{ 'uni-list-item__content--center': thumb || showExtraIcon || showBadge || showSwitch }\">\n\t\t\t\t\t\t<text v-if=\"title\" class=\"uni-list-item__content-title\"\n\t\t\t\t\t\t\t:class=\"[ellipsis !== 0 && ellipsis <= 2 ? 'uni-ellipsis-' + ellipsis : '']\">{{ title }}</text>\n\t\t\t\t\t\t<text v-if=\"note\" class=\"uni-list-item__content-note\">{{ note }}</text>\n\t\t\t\t\t</view>\n\t\t\t\t</slot>\n\t\t\t\t<slot name=\"footer\">\n\t\t\t\t\t<view v-if=\"rightText || showBadge || showSwitch\" class=\"uni-list-item__extra\"\n\t\t\t\t\t\t:class=\"{ 'flex--justify': direction === 'column' }\">\n\t\t\t\t\t\t<text v-if=\"rightText\" class=\"uni-list-item__extra-text\">{{ rightText }}</text>\n\t\t\t\t\t\t<uni-badge v-if=\"showBadge\" :type=\"badgeType\" :text=\"badgeText\" :custom-style=\"badgeStyle\" />\n\t\t\t\t\t\t<switch v-if=\"showSwitch\" :disabled=\"disabled\" :checked=\"switchChecked\"\n\t\t\t\t\t\t\t@change=\"onSwitchChange\" />\n\t\t\t\t\t</view>\n\t\t\t\t</slot>\n\t\t\t</view>\n\t\t\t<uni-icons v-if=\"showArrow || link\" :size=\"16\" class=\"uni-icon-wrapper\" color=\"#bbb\" type=\"arrowright\" />\n\t\t</view>\n\t\t<!-- #ifdef APP-NVUE -->\n\t</cell>\n\t<!-- #endif -->\n</template>\n\n<script>\n\t/**\n\t * ListItem 列表子组件\n\t * @description 列表子组件\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=24\n\t * @property {String} \ttitle \t\t\t\t\t\t\t标题\n\t * @property {String} \tnote \t\t\t\t\t\t\t描述\n\t * @property {String} \tthumb \t\t\t\t\t\t\t左侧缩略图，若thumb有值，则不会显示扩展图标\n\t * @property {String}  \tthumbSize = [lg|base|sm]\t\t略缩图大小\n\t * \t@value \t lg\t\t\t大图\n\t * \t@value \t base\t\t一般\n\t * \t@value \t sm\t\t\t小图\n\t * @property {String} \tbadgeText\t\t\t\t\t\t数字角标内容\n\t * @property {String} \tbadgeType \t\t\t\t\t\t数字角标类型，参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21)\n\t * @property {Object}   badgeStyle           数字角标样式\n\t * @property {String} \trightText \t\t\t\t\t\t右侧文字内容\n\t * @property {Boolean} \tdisabled = [true|false]\t\t\t是否禁用\n\t * @property {Boolean} \tclickable = [true|false] \t\t是否开启点击反馈\n\t * @property {String} \tlink = [navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈\n\t *  @value \tnavigateTo \t同 uni.navigateTo()\n\t * \t@value redirectTo \t同 uni.redirectTo()\n\t * \t@value reLaunch   \t同 uni.reLaunch()\n\t * \t@value switchTab  \t同 uni.switchTab()\n\t * @property {String | PageURIString} \tto  \t\t\t跳转目标页面\n\t * @property {Boolean} \tshowBadge = [true|false] \t\t是否显示数字角标\n\t * @property {Boolean} \tshowSwitch = [true|false] \t\t是否显示Switch\n\t * @property {Boolean} \tswitchChecked = [true|false] \tSwitch是否被选中\n\t * @property {Boolean} \tshowExtraIcon = [true|false] \t左侧是否显示扩展图标\n\t * @property {Object} \textraIcon \t\t\t\t\t\t扩展图标参数，格式为 {color: '#4cd964',size: '22',type: 'spinner'}\n\t * @property {String} \tdirection = [row|column]\t\t排版方向\n\t * @value row \t\t\t水平排列\n\t * @value column \t\t垂直排列\n\t * @event {Function} \tclick \t\t\t\t\t\t\t点击 uniListItem 触发事件\n\t * @event {Function} \tswitchChange \t\t\t\t\t点击切换 Switch 时触发\n\t */\n\texport default {\n\t\tname: 'UniListItem',\n\t\temits: ['click', 'switchChange'],\n\t\tprops: {\n\t\t\tdirection: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'row'\n\t\t\t},\n\t\t\ttitle: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tnote: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tellipsis: {\n\t\t\t\ttype: [Number,String],\n\t\t\t\tdefault: 0\n\t\t\t},\n\t\t\tdisabled: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tclickable: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tshowArrow: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tlink: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tto: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tshowBadge: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tshowSwitch: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tswitchChecked: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\tbadgeText: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tbadgeType: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'success'\n\t\t\t},\n\t\t\tbadgeStyle:{\n\t\t\t\ttype: Object,\n\t\t\t\tdefault () {\n\t\t\t\t\treturn {}\n\t\t\t\t}\n\t\t\t},\n\t\t\trightText: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tthumb: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\tthumbSize: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'base'\n\t\t\t},\n\t\t\tshowExtraIcon: {\n\t\t\t\ttype: [Boolean, String],\n\t\t\t\tdefault: false\n\t\t\t},\n\t\t\textraIcon: {\n\t\t\t\ttype: Object,\n\t\t\t\tdefault () {\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: '',\n\t\t\t\t\t\tcolor: '#000000',\n\t\t\t\t\t\tsize: 20\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t},\n\t\t\tborder: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t}\n\t\t},\n\t\t// inject: ['list'],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisFirstChild: false\n\t\t\t};\n\t\t},\n\t\tmounted() {\n\t\t\tthis.list = this.getForm()\n\t\t\t// 判断是否存在 uni-list 组件\n\t\t\tif (this.list) {\n\t\t\t\tif (!this.list.firstChildAppend) {\n\t\t\t\t\tthis.list.firstChildAppend = true;\n\t\t\t\t\tthis.isFirstChild = true;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t/**\n\t\t\t * 获取父元素实例\n\t\t\t */\n\t\t\tgetForm(name = 'uniList') {\n\t\t\t\tlet parent = this.$parent;\n\t\t\t\tlet parentName = parent.$options.name;\n\t\t\t\twhile (parentName !== name) {\n\t\t\t\t\tparent = parent.$parent;\n\t\t\t\t\tif (!parent) return false\n\t\t\t\t\tparentName = parent.$options.name;\n\t\t\t\t}\n\t\t\t\treturn parent;\n\t\t\t},\n\t\t\tonClick() {\n\t\t\t\tif (this.to !== '') {\n\t\t\t\t\tthis.openPage();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (this.clickable || this.link) {\n\t\t\t\t\tthis.$emit('click', {\n\t\t\t\t\t\tdata: {}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\tonSwitchChange(e) {\n\t\t\t\tthis.$emit('switchChange', e.detail);\n\t\t\t},\n\t\t\topenPage() {\n\t\t\t\tif (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {\n\t\t\t\t\tthis.pageApi(this.link);\n\t\t\t\t} else {\n\t\t\t\t\tthis.pageApi('navigateTo');\n\t\t\t\t}\n\t\t\t},\n\t\t\tpageApi(api) {\n\t\t\t\tlet callback = {\n\t\t\t\t\turl: this.to,\n\t\t\t\t\tsuccess: res => {\n\t\t\t\t\t\tthis.$emit('click', {\n\t\t\t\t\t\t\tdata: res\n\t\t\t\t\t\t});\n\t\t\t\t\t},\n\t\t\t\t\tfail: err => {\n\t\t\t\t\t\tthis.$emit('click', {\n\t\t\t\t\t\t\tdata: err\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tswitch (api) {\n\t\t\t\t\tcase 'navigateTo':\n\t\t\t\t\t\tuni.navigateTo(callback)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'redirectTo':\n\t\t\t\t\t\tuni.redirectTo(callback)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'reLaunch':\n\t\t\t\t\t\tuni.reLaunch(callback)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'switchTab':\n\t\t\t\t\t\tuni.switchTab(callback)\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\tuni.navigateTo(callback)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\">\n\t$uni-font-size-sm:12px;\n\t$uni-font-size-base:14px;\n\t$uni-font-size-lg:16px;\n\t$uni-spacing-col-lg: 12px;\n\t$uni-spacing-row-lg: 15px;\n\t$uni-img-size-sm:20px;\n\t$uni-img-size-base:26px;\n\t$uni-img-size-lg:40px;\n\t$uni-border-color:#e5e5e5;\n\t$uni-bg-color-hover:#f1f1f1;\n\t$uni-text-color-grey:#999;\n\t$list-item-pd: $uni-spacing-col-lg $uni-spacing-row-lg;\n\t.uni-list-item {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tfont-size: $uni-font-size-lg;\n\t\tposition: relative;\n\t\tjustify-content: space-between;\n\t\talign-items: center;\n\t\tbackground-color: #fff;\n\t\tflex-direction: row;\n\t\t/* #ifdef H5 */\n\t\tcursor: pointer;\n\t\t/* #endif */\n\t}\n\t.uni-list-item--disabled {\n\t\topacity: 0.3;\n\t}\n\t.uni-list-item--hover {\n\t\tbackground-color: $uni-bg-color-hover;\n\t}\n\t.uni-list-item__container {\n\t\tposition: relative;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tpadding: $list-item-pd;\n\t\tpadding-left: $uni-spacing-row-lg;\n\t\tflex: 1;\n\t\toverflow: hidden;\n\t\t// align-items: center;\n\t}\n\t.container--right {\n\t\tpadding-right: 0;\n\t}\n\t// .border--left {\n\t// \tmargin-left: $uni-spacing-row-lg;\n\t// }\n\t.uni-list--border {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tleft: 0;\n\t\t/* #ifdef APP-NVUE */\n\t\tborder-top-color: $uni-border-color;\n\t\tborder-top-style: solid;\n\t\tborder-top-width: 0.5px;\n\t\t/* #endif */\n\t}\n\t/* #ifndef APP-NVUE */\n\t.uni-list--border:after {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tright: 0;\n\t\tleft: 0;\n\t\theight: 1px;\n\t\tcontent: '';\n\t\t-webkit-transform: scaleY(0.5);\n\t\ttransform: scaleY(0.5);\n\t\tbackground-color: $uni-border-color;\n\t}\n\t/* #endif */\n\t.uni-list-item__content {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tpadding-right: 8px;\n\t\tflex: 1;\n\t\tcolor: #3b4144;\n\t\t// overflow: hidden;\n\t\tflex-direction: column;\n\t\tjustify-content: space-between;\n\t\toverflow: hidden;\n\t}\n\t.uni-list-item__content--center {\n\t\tjustify-content: center;\n\t}\n\t.uni-list-item__content-title {\n\t\tfont-size: $uni-font-size-base;\n\t\tcolor: #3b4144;\n\t\toverflow: hidden;\n\t}\n\t.uni-list-item__content-note {\n\t\tmargin-top: 6rpx;\n\t\tcolor: $uni-text-color-grey;\n\t\tfont-size: $uni-font-size-sm;\n\t\toverflow: hidden;\n\t}\n\t.uni-list-item__extra {\n\t\t// width: 25%;\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\tjustify-content: flex-end;\n\t\talign-items: center;\n\t}\n\t.uni-list-item__header {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t}\n\t.uni-list-item__icon {\n\t\tmargin-right: 18rpx;\n\t\tflex-direction: row;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t}\n\t.uni-list-item__icon-img {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: block;\n\t\t/* #endif */\n\t\theight: $uni-img-size-base;\n\t\twidth: $uni-img-size-base;\n\t\tmargin-right: 10px;\n\t}\n\t.uni-icon-wrapper {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\talign-items: center;\n\t\tpadding: 0 10px;\n\t}\n\t.flex--direction {\n\t\tflex-direction: column;\n\t\t/* #ifndef APP-NVUE */\n\t\talign-items: initial;\n\t\t/* #endif */\n\t}\n\t.flex--justify {\n\t\t/* #ifndef APP-NVUE */\n\t\tjustify-content: initial;\n\t\t/* #endif */\n\t}\n\t.uni-list--lg {\n\t\theight: $uni-img-size-lg;\n\t\twidth: $uni-img-size-lg;\n\t}\n\t.uni-list--base {\n\t\theight: $uni-img-size-base;\n\t\twidth: $uni-img-size-base;\n\t}\n\t.uni-list--sm {\n\t\theight: $uni-img-size-sm;\n\t\twidth: $uni-img-size-sm;\n\t}\n\t.uni-list-item__extra-text {\n\t\tcolor: $uni-text-color-grey;\n\t\tfont-size: $uni-font-size-sm;\n\t}\n\t.uni-ellipsis-1 {\n\t\t/* #ifndef APP-NVUE */\n\t\toverflow: hidden;\n\t\twhite-space: nowrap;\n\t\ttext-overflow: ellipsis;\n\t\t/* #endif */\n\t\t/* #ifdef APP-NVUE */\n\t\tlines: 1;\n\t\ttext-overflow:ellipsis;\n\t\t/* #endif */\n\t}\n\t.uni-ellipsis-2 {\n\t\t/* #ifndef APP-NVUE */\n\t\toverflow: hidden;\n\t\ttext-overflow: ellipsis;\n\t\tdisplay: -webkit-box;\n\t\t-webkit-line-clamp: 2;\n\t\t-webkit-box-orient: vertical;\n\t\t/* #endif */\n\t\t/* #ifdef APP-NVUE */\n\t\tlines: 2;\n\t\ttext-overflow:ellipsis;\n\t\t/* #endif */\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uni-list/package.json",
    "content": "{\n  \"id\": \"uni-list\",\n  \"displayName\": \"uni-list 列表\",\n  \"version\": \"1.2.1\",\n  \"description\": \"List 组件 ，帮助使用者快速构建列表。\",\n  \"keywords\": [\n    \"\",\n    \"uni-ui\",\n    \"uniui\",\n    \"列表\",\n    \"\",\n    \"list\"\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      \"uni-badge\",\n      \"uni-icons\"\n    ],\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": "bookkeeping_user_uniapp/uni_modules/uni-list/readme.md",
    "content": "## List 列表\n> **组件名：uni-list**\n> 代码块： `uList`、`uListItem`\n> 关联组件：`uni-list-item`、`uni-badge`、`uni-icons`、`uni-list-chat`、`uni-list-ad`\n\n\nList 列表组件，包含基本列表样式、可扩展插槽机制、长列表性能优化、多端兼容。\n\n在vue页面里，它默认使用页面级滚动。在app-nvue页面里，它默认使用原生list组件滚动。这样的长列表，在滚动出屏幕外后，系统会回收不可见区域的渲染内存资源，不会造成滚动越长手机越卡的问题。\n\nuni-list组件是父容器，里面的核心是uni-list-item子组件，它代表列表中的一个可重复行，子组件可以无限循环。\n\nuni-list-item有很多风格，uni-list-item组件通过内置的属性，满足一些常用的场景。当内置属性不满足需求时，可以通过扩展插槽来自定义列表内容。\n\n内置属性可以覆盖的场景包括：导航列表、设置列表、小图标列表、通信录列表、聊天记录列表。\n\n涉及很多大图或丰富内容的列表，比如类今日头条的新闻列表、类淘宝的电商列表，需要通过扩展插槽实现。\n\n下文均有样例给出。\n\nuni-list不包含下拉刷新和上拉翻页。上拉翻页另见组件：[uni-load-more](https://ext.dcloud.net.cn/plugin?id=29)\n\n\n### 安装方式\n\n本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范，`HBuilderX 2.5.5`起，只需将本组件导入项目，在页面`template`中即可直接使用，无需在页面中`import`和注册`components`。\n\n如需通过`npm`方式使用`uni-ui`组件，另见文档：[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)\n\n> **注意事项**\n> 为了避免错误使用，给大家带来不好的开发体验，请在使用组件前仔细阅读下面的注意事项，可以帮你避免一些错误。\n> - 组件需要依赖 `sass` 插件 ，请自行手动安装\n> - 组件内部依赖 `'uni-icons'` 、`uni-badge` 组件\n> - `uni-list` 和 `uni-list-item` 需要配套使用，暂不支持单独使用 `uni-list-item`\n> - 只有开启点击反馈后，会有点击选中效果\n> - 使用插槽时，可以完全自定义内容\n> - note 、rightText 属性暂时没做限制，不支持文字溢出隐藏，使用时应该控制长度显示或通过默认插槽自行扩展\n> - 支付宝小程序平台需要在支付宝小程序开发者工具里开启 component2 编译模式，开启方式： 详情 --> 项目配置 --> 启用 component2 编译\n> - 如果需要修改 `switch`、`badge` 样式，请使用插槽自定义\n> - 在 `HBuilderX` 低版本中，可能会出现组件显示 `undefined` 的问题，请升级最新的 `HBuilderX` 或者 `cli`\n> - 如使用过程中有任何问题，或者您对uni-ui有一些好的建议，欢迎加入 uni-ui 交流群：871950839\n \n\n### 基本用法 \n\n- 设置 `title` 属性，可以显示列表标题\n- 设置 `disabled` 属性，可以禁用当前项\n\n```html\n<uni-list>\n\t<uni-list-item  title=\"列表文字\" ></uni-list-item>\n\t<uni-list-item :disabled=\"true\" title=\"列表禁用状态\" ></uni-list-item>\n</uni-list>\n\t\t\t \n```\n\n### 多行内容显示\n\n- 设置 `note` 属性 ，可以在第二行显示描述文本信息\n\n```html\n<uni-list>\n\t<uni-list-item title=\"列表文字\" note=\"列表描述信息\"></uni-list-item>\n\t<uni-list-item :disabled=\"true\" title=\"列表文字\" note=\"列表禁用状态\"></uni-list-item>\n</uni-list>\n\n```\n\n### 右侧显示角标、switch\n\n- 设置 `show-badge` 属性 ，可以显示角标内容\n- 设置 `show-switch` 属性，可以显示 switch 开关\n\n```html\n<uni-list>\n\t<uni-list-item  title=\"列表右侧显示角标\" :show-badge=\"true\" badge-text=\"12\" ></uni-list-item>\n\t<uni-list-item title=\"列表右侧显示 switch\"  :show-switch=\"true\"  @switchChange=\"switchChange\" ></uni-list-item>\n</uni-list>\n\n```\n\n### 左侧显示略缩图、图标  \n\n- 设置 `thumb` 属性 ，可以在列表左侧显示略缩图\n- 设置 `show-extra-icon` 属性，并指定 `extra-icon` 可以在左侧显示图标\n\n```html\n <uni-list>\n \t<uni-list-item title=\"列表左侧带略缩图\" note=\"列表描述信息\" thumb=\"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png\"\n \t thumb-size=\"lg\" rightText=\"右侧文字\"></uni-list-item>\n \t<uni-list-item :show-extra-icon=\"true\" :extra-icon=\"extraIcon1\" title=\"列表左侧带扩展图标\" ></uni-list-item>\n</uni-list>\n```\n\n### 开启点击反馈和右侧箭头\n- 设置 `clickable` 为 `true` ，则表示这是一个可点击的列表，会默认给一个点击效果，并可以监听 `click` 事件\n- 设置 `link` 属性，会自动开启点击反馈，并给列表右侧添加一个箭头\n- 设置 `to` 属性，可以跳转页面，`link` 的值表示跳转方式，如果不指定，默认为 `navigateTo`\n\n```html\n\n<uni-list>\n\t<uni-list-item title=\"开启点击反馈\" clickable  @click=\"onClick\" ></uni-list-item>\n\t<uni-list-item title=\"默认 navigateTo 方式跳转页面\" link to=\"/pages/vue/index/index\" @click=\"onClick($event,1)\" ></uni-list-item>\n\t<uni-list-item title=\"reLaunch 方式跳转页面\" link=\"reLaunch\" to=\"/pages/vue/index/index\" @click=\"onClick($event,1)\" ></uni-list-item>\n</uni-list>\n\n```\n\n\n### 聊天列表示例\n- 设置 `clickable` 为 `true` ，则表示这是一个可点击的列表，会默认给一个点击效果，并可以监听 `click` 事件\n- 设置 `link` 属性，会自动开启点击反馈，`link` 的值表示跳转方式，如果不指定，默认为 `navigateTo`\n- 设置 `to` 属性，可以跳转页面\n- `time` 属性，通常会设置成时间显示，但是这个属性不仅仅可以设置时间，你可以传入任何文本，注意文本长度可能会影响显示\n- `avatar` 和 `avatarList` 属性同时只会有一个生效，同时设置的话，`avatarList` 属性的长度大于1 ，`avatar` 属性将失效\n- 可以通过默认插槽自定义列表右侧内容\n\n```html\n\n<uni-list>\n\t<uni-list :border=\"true\">\n\t\t<!-- 显示圆形头像 -->\n\t\t<uni-list-chat :avatar-circle=\"true\" title=\"uni-app\" avatar=\"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png\" note=\"您收到一条新的消息\" time=\"2020-02-02 20:20\" ></uni-list-chat>\n\t\t<!-- 右侧带角标 -->\n\t\t<uni-list-chat title=\"uni-app\" avatar=\"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png\" note=\"您收到一条新的消息\" time=\"2020-02-02 20:20\" badge-text=\"12\" :badge-style=\"{backgroundColor:'#FF80AB'}\"></uni-list-chat>\n\t\t<!-- 头像显示圆点 -->\n\t\t<uni-list-chat title=\"uni-app\" avatar=\"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png\" note=\"您收到一条新的消息\" time=\"2020-02-02 20:20\" badge-positon=\"left\" badge-text=\"dot\"></uni-list-chat>\n\t\t<!-- 头像显示角标 -->\n\t\t<uni-list-chat title=\"uni-app\" avatar=\"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png\" note=\"您收到一条新的消息\" time=\"2020-02-02 20:20\" badge-positon=\"left\" badge-text=\"99\"></uni-list-chat>\n\t\t<!-- 显示多头像 -->\n\t\t<uni-list-chat title=\"uni-app\" :avatar-list=\"avatarList\" note=\"您收到一条新的消息\" time=\"2020-02-02 20:20\" badge-positon=\"left\" badge-text=\"dot\"></uni-list-chat>\n\t\t<!-- 自定义右侧内容 -->\n\t\t<uni-list-chat title=\"uni-app\" :avatar-list=\"avatarList\" note=\"您收到一条新的消息\" time=\"2020-02-02 20:20\" badge-positon=\"left\" badge-text=\"dot\">\n\t\t\t<view class=\"chat-custom-right\">\n\t\t\t\t<text class=\"chat-custom-text\">刚刚</text>\n\t\t\t\t<!-- 需要使用 uni-icons 请自行引入 -->\n\t\t\t\t<uni-icons type=\"star-filled\" color=\"#999\" size=\"18\"></uni-icons>\n\t\t\t</view>\n\t\t</uni-list-chat>\n\t</uni-list>\n</uni-list>\n\n```\n\n```javascript\n\nexport default {\n\tcomponents: {},\n\tdata() {\n\t\treturn {\n\t\t\tavatarList: [{\n\t\t\t\turl: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'\n\t\t\t}, {\n\t\t\t\turl: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'\n\t\t\t}, {\n\t\t\t\turl: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'\n\t\t\t}]\n\t\t}\n\t}\n}\n\n```\n\n\n```css\n\n.chat-custom-right {\n\tflex: 1;\n\t/* #ifndef APP-NVUE */\n\tdisplay: flex;\n\t/* #endif */\n\tflex-direction: column;\n\tjustify-content: space-between;\n\talign-items: flex-end;\n}\n\n.chat-custom-text {\n\tfont-size: 12px;\n\tcolor: #999;\n}\n\n```\n\n## API\n\n### List Props\n\n属性名\t\t\t|类型\t\t|默认值\t\t|\t说明\t\t\t\t\t\t\t\t\t\t\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\nborder\t\t\t|Boolean\t|true\t\t|\t是否显示边框\n\n\n### ListItem Props\n\n属性名\t\t\t|类型\t\t|默认值\t\t|\t说明\t\t\t\t\t\t\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\ntitle\t\t\t|String\t\t|-\t\t\t|\t标题\nnote\t\t\t|String\t\t|-\t\t\t|\t描述\nellipsis\t\t|Number\t\t|0\t\t\t|\ttitle 是否溢出隐藏，可选值，0:默认;  1:显示一行;\t2:显示两行;【nvue 暂不支持】\nthumb\t\t\t|String\t\t|-\t\t\t|\t左侧缩略图，若thumb有值，则不会显示扩展图标\nthumbSize\t\t|String \t|medium \t|\t略缩图尺寸，可选值，lg:大图;  medium:一般;\tsm:小图;\nshowBadge\t\t|Boolean\t|false\t\t|\t是否显示数字角标\t\nbadgeText\t\t|String\t\t|-\t\t\t|\t数字角标内容\nbadgeType\t\t|String\t\t|-\t\t\t|\t数字角标类型，参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21)\t\nbadgeStyle  |Object   |-      | 数字角标样式，使用uni-badge的custom-style参数\nrightText\t\t|String\t\t|-\t\t\t|\t右侧文字内容\ndisabled\t\t|Boolean\t|false\t\t|\t是否禁用\t\nshowArrow \t\t|Boolean\t|true\t\t|\t是否显示箭头图标\t\t\t\nlink\t\t\t|String \t|navigateTo\t|\t新页面跳转方式，可选值见下表\nto\t\t\t\t|String\t\t|-\t\t\t|\t新页面跳转地址，如填写此属性，click 会返回页面是否跳转成功\t\t\t\nclickable\t\t|Boolean\t|false\t\t|\t是否开启点击反馈\nshowSwitch\t    |Boolean\t|false\t\t|\t是否显示Switch\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\nswitchChecked\t|Boolean\t|false\t\t|\tSwitch是否被选中\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\nshowExtraIcon   |Boolean\t|false\t\t|\t左侧是否显示扩展图标\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\nextraIcon\t\t|Object\t\t|-\t\t\t|\t扩展图标参数，格式为 ``{color: '#4cd964',size: '22',type: 'spinner'}``，参考 [uni-icons](https://ext.dcloud.net.cn/plugin?id=28)\t\ndirection\t\t| String\t|row\t\t|\t排版方向，可选值，row:水平排列;  column:垂直排列; 3个插槽是水平排还是垂直排，也受此属性控制\n\n\n#### Link Options\n\n属性名\t\t\t\t|\t说明\n:-:\t\t\t\t\t|\t:-:\nnavigateTo \t| \t同 uni.navigateTo()\nredirectTo \t|\t同 uni.reLaunch()\nreLaunch\t\t|\t同 uni.reLaunch()\nswitchTab  \t|\t同 uni.switchTab()\n\n### ListItem Events\n\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\t\t\t\nclick\t\t\t|点击 uniListItem 触发事件，需开启点击反馈\t|-\t\t\t\t\t\nswitchChange\t|点击切换 Switch 时触发，需显示 switch\t\t|e={value:checked}\t\n\n\n\n### ListItem Slots\n\n名称\t \t|\t说明\t\t\t\t\t\n:-:\t\t|\t:-:\t\t\t\t\t\t\nheader\t|\t左/上内容插槽，可完全自定义默认显示\nbody\t|\t中间内容插槽，可完全自定义中间内容\t\t\t\t\nfooter\t|\t右/下内容插槽，可完全自定义右侧内容\t\t\n\n\n> **通过插槽扩展**\n> 需要注意的是当使用插槽时，内置样式将会失效，只保留排版样式，此时的样式需要开发者自己实现\n> 如果\t`uni-list-item` 组件内置属性样式无法满足需求，可以使用插槽来自定义uni-list-item里的内容。\n> uni-list-item提供了3个可扩展的插槽：`header`、`body`、`footer`\n> - 当 `direction` 属性为 `row` 时表示水平排列，此时 `header` 表示列表的左边部分，`body` 表示列表的中间部分，`footer` 表示列表的右边部分\n> - 当 `direction` 属性为 `column` 时表示垂直排列，此时 `header` 表示列表的上边部分，`body` 表示列表的中间部分，`footer` 表示列表的下边部分\n> 开发者可以只用1个插槽，也可以3个一起使用。在插槽中可自主编写view标签，实现自己所需的效果。\n\n\t\n**示例**\n\n```html\n<uni-list>\n\t<uni-list-item title=\"自定义右侧插槽\" note=\"列表描述信息\" link>\n\t\t<template slot=\"header\">\n\t\t\t<image class=\"slot-image\" src=\"/static/logo.png\" mode=\"widthFix\"></image>\n\t\t</template>\n\t</uni-list-item>\n\t<uni-list-item>\n\t\t<!-- 自定义 header -->\n\t\t<view slot=\"header\" class=\"slot-box\"><image class=\"slot-image\" src=\"/static/logo.png\" mode=\"widthFix\"></image></view>\n\t\t<!-- 自定义 body -->\n\t\t<text slot=\"body\" class=\"slot-box slot-text\">自定义插槽</text>\n\t\t<!-- 自定义 footer-->\n\t\t<template slot=\"footer\">\n\t\t\t<image class=\"slot-image\" src=\"/static/logo.png\" mode=\"widthFix\"></image>\n\t\t</template>\n\t</uni-list-item>\n</uni-list>\n```\n\n\n\n\n\n### ListItemChat Props\n\n属性名\t\t\t|类型\t\t|默认值\t\t|\t说明\t\t\t\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\ntitle \t\t\t|String\t\t|-\t\t\t|\t标题\nnote \t\t\t|String\t\t|-\t\t\t|\t描述\nclickable\t\t|Boolean\t|false\t\t|\t是否开启点击反馈\nbadgeText\t\t|String\t\t|-\t\t\t|\t数字角标内容，设置为 `dot` 将显示圆点\nbadgePositon \t|String\t\t|right\t\t|\t角标位置\nlink\t\t\t|String \t|navigateTo\t|\t是否展示右侧箭头并开启点击反馈，可选值见下表\nclickable\t\t|Boolean\t|false\t\t|\t是否开启点击反馈\nto\t\t\t\t|String\t\t|-\t\t\t|\t跳转页面地址，如填写此属性，click 会返回页面是否跳转成功\t\ntime\t\t\t|String \t|-\t\t\t|\t右侧时间显示\navatarCircle \t|Boolean \t|false\t\t|\t是否显示圆形头像\navatar\t\t\t|String \t|-\t\t\t|\t头像地址，avatarCircle 不填时生效\navatarList \t\t|Array\t \t|-\t\t\t|\t头像组，格式为 [{url:''}]\n\n#### Link Options\n\n属性名\t\t|\t说明\n:-:\t\t\t|\t:-:\nnavigateTo \t| \t同 uni.navigateTo()\nredirectTo \t|\t同 uni.reLaunch()\nreLaunch\t|\t同 uni.reLaunch()\nswitchTab  \t|\t同 uni.switchTab()\n\n### ListItemChat Slots\n\n名称\t \t|\t说明\t\t\t\t\t\n:-\t\t|\t:-\t\t\t\t\t\t\ndefault\t|\t自定义列表右侧内容（包括时间和角标显示）\n\n### ListItemChat Events\n事件称名\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\n@click\t\t\t|\t点击 uniListChat 触发事件\t|\t{data:{}}\t，如有 to 属性，会返回页面跳转信息\t\n\n\n\n\n\n\n## 基于uni-list扩展的页面模板\n\n通过扩展插槽，可实现多种常见样式的列表\n\n**新闻列表类**\n\n1. 云端一体混合布局：[https://ext.dcloud.net.cn/plugin?id=2546](https://ext.dcloud.net.cn/plugin?id=2546)\n2. 云端一体垂直布局，大图模式：[https://ext.dcloud.net.cn/plugin?id=2583](https://ext.dcloud.net.cn/plugin?id=2583)\n3. 云端一体垂直布局，多行图文混排：[https://ext.dcloud.net.cn/plugin?id=2584](https://ext.dcloud.net.cn/plugin?id=2584)\n4. 云端一体垂直布局，多图模式：[https://ext.dcloud.net.cn/plugin?id=2585](https://ext.dcloud.net.cn/plugin?id=2585)\n5. 云端一体水平布局，左图右文：[https://ext.dcloud.net.cn/plugin?id=2586](https://ext.dcloud.net.cn/plugin?id=2586)\n6. 云端一体水平布局，左文右图：[https://ext.dcloud.net.cn/plugin?id=2587](https://ext.dcloud.net.cn/plugin?id=2587)\n7. 云端一体垂直布局，无图模式，主标题+副标题：[https://ext.dcloud.net.cn/plugin?id=2588](https://ext.dcloud.net.cn/plugin?id=2588)\n\n**商品列表类**\n\n1. 云端一体列表/宫格视图互切：[https://ext.dcloud.net.cn/plugin?id=2651](https://ext.dcloud.net.cn/plugin?id=2651)\n2. 云端一体列表（宫格模式）：[https://ext.dcloud.net.cn/plugin?id=2671](https://ext.dcloud.net.cn/plugin?id=2671)\n3. 云端一体列表（列表模式）：[https://ext.dcloud.net.cn/plugin?id=2672](https://ext.dcloud.net.cn/plugin?id=2672)\n\n## 组件示例\n\n点击查看：[https://hellouniapp.dcloud.net.cn/pages/extUI/list/list](https://hellouniapp.dcloud.net.cn/pages/extUI/list/list)"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 www.uviewui.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/README.md",
    "content": "<p align=\"center\">\n    <img alt=\"logo\" src=\"https://uviewui.com/common/logo.png\" width=\"120\" height=\"120\" style=\"margin-bottom: 10px;\">\n</p>\n<h3 align=\"center\" style=\"margin: 30px 0 30px;font-weight: bold;font-size:40px;\">uView 2.0</h3>\n<h3 align=\"center\">多平台快速开发的UI框架</h3>\n\n[![stars](https://img.shields.io/github/stars/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)\n[![forks](https://img.shields.io/github/forks/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)\n[![issues](https://img.shields.io/github/issues/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0/issues)\n[![Website](https://img.shields.io/badge/uView-up-blue?style=flat-square)](https://uviewui.com)\n[![release](https://img.shields.io/github/v/release/umicro/uView2.0?style=flat-square)](https://gitee.com/umicro/uView2.0/releases)\n[![license](https://img.shields.io/github/license/umicro/uView2.0?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)\n\n## 说明\n\nuView UI，是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架，全面的组件和便捷的工具会让您信手拈来，如鱼得水\n\n## [官方文档：https://uviewui.com](https://uviewui.com)\n\n\n## 预览\n\n您可以通过**微信**扫码，查看最佳的演示效果。\n<br>\n<br>\n<img src=\"https://uviewui.com/common/weixin_mini_qrcode.png\" width=\"220\" height=\"220\" >\n\n\n## 链接\n\n- [官方文档](https://www.uviewui.com/)\n- [更新日志](https://www.uviewui.com/components/changelog.html)\n- [升级指南](https://www.uviewui.com/components/changeGuide.html)\n- [关于我们](https://www.uviewui.com/cooperation/about.html)\n\n## 交流反馈\n\n欢迎加入我们的QQ群交流反馈：[点此跳转](https://www.uviewui.com/components/addQQGroup.html)\n\n## 关于PR\n\n> 我们非常乐意接受各位的优质PR，但在此之前我希望您了解uView2.0是一个需要兼容多个平台的（小程序、h5、ios app、android app）包括nvue页面、vue页面。\n> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢！\n\n## 安装\n\n#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?id=1593](https://ext.dcloud.net.cn/plugin?id=1593)\n\n请通过[官网安装文档](https://www.uviewui.com/components/install.html)了解更详细的内容\n\n## 快速上手\n\n请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容\n\n## 使用方法\n配置easycom规则后，自动按需引入，无需`import`组件，直接引用即可。\n\n```html\n<template>\n\t<u-button text=\"按钮\"></u-button>\n</template>\n```\n\n## 版权信息\nuView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议，意味着您无需支付任何费用，也无需授权，即可将uView应用到您的产品中。\n\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/changelog.md",
    "content": "## 2.0.34（2022-09-25）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性\n2. 修复`route`方法调用可能报错的问题\n3. 修复`u-no-network`组件`z-index`无效的问题\n4. 修复`textarea`组件在h5上confirmType=\"\"报错的问题\n5. `u-rate`适配`nvue`\n6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划（2017年版）》进行修改。)\n7. `form-item`添加`labelPosition`属性\n8. `u-calendar`修复`maxDate`设置为当前日期，并且当前时间大于08：00时无法显示日期列表的问题 (#724)\n9. `u-radio`增加一个默认插槽用于自定义修改label内容 (#680)\n10. 修复`timeFormat`函数在safari重的兼容性问题 (#664)\n## 2.0.33（2022-06-17）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复`loadmore`组件`lineColor`类型错误问题\n2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题\n## 2.0.32（2022-06-16）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n1. `u-loadmore`新增自定义颜色、虚/实线\n2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题\n3. 修复`u-list`回弹问题\n4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题\n5. `u-loading-page`添加控制图标大小的属性`iconSize`\n6. 修复`u-tooltip`组件`color`参数不生效的问题\n7. 修复`u--input`组件使用`blur`事件输出为`undefined`的bug\n8. `u-code-input`组件新增键盘弹起时，是否自动上推页面参数`adjustPosition`\n9. 修复`image`组件`load`事件无回调对象问题\n10. 修复`button`组件`loadingSize`设置无效问题\n10. 其他修复\n## 2.0.31（2022-04-19）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题\n2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题\n3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题（`app`去除此功能）\n4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题\n5. 其他修复\n## 2.0.30（2022-04-04）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. `u-rate`增加`readonly`属性\n2. `tabs`滑块支持设置背景图片\n3. 修复`u-subsection` `mode`为`subsection`时，滑块样式不正确的问题\n4. `u-code-input`添加光标效果动画\n5. 修复`popup`的`open`事件不触发\n6. 修复`u-flex-column`无效的问题\n7. 修复`u-datetime-picker`索引在特定场合异常问题\n8. 修复`u-datetime-picker`最小时间字符串模板错误问题\n9. `u-swiper`添加`m3u8`验证\n10. `u-swiper`修改判断image和video逻辑\n11. 修复`swiper`无法使用本地图片问题，增加`type`参数\n12. 修复`u-row-notice`格式错误问题\n13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题\n14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题\n15. 修复`upload`组件条件编译位置判断错误，导致`previewImage`属性设置为`false`时，整个组件都会被隐藏的问题\n16. 修复`u-checkbox-group`设置`shape`属性无效的问题\n17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题\n18. 修复`u-action-sheet`组件，关闭事件逻辑错误的问题\n19. 修复`u-list`触顶事件的触发错误的问题\n20. 修复`u-text`只有手机号可拨打的问题\n21. 修复`u-textarea`不能换行的问题\n22. 其他修复\n## 2.0.29（2022-03-13）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复`u--text`组件设置`decoration`属性未生效的问题\n2. 修复`u-datetime-picker`使用`formatter`后返回值不正确\n3. 修复`u-datetime-picker` `intercept` 可能为undefined\n4. 修复已设置单位 uni..config.unit = 'rpx'时，线型指示器 `transform` 的位置翻倍，导致指示器超出宽度\n5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效\n6. 修复默认值传值为空的时候，打开`u-datetime-picker`报错，不能选中第一列时间的bug\n7. 修复`u-datetime-picker`使用`formatter`后返回值不正确\n8. 修复`u-image`组件`loading`无效果的问题\n9. 修复`config.unit`属性设为`rpx`时，导航栏占用高度不足导致塌陷的问题\n10. 修复`u-datetime-picker`组件`itemHeight`无效问题\n11. 其他修复\n## 2.0.28（2022-02-22）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. search组件新增searchIconSize属性\n2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56\n3. 修复text value.js 判断日期出format错误问题\n4. priceFormat格式化金额出现精度错误\n5. priceFormat在部分情况下出现精度损失问题\n6. 优化表单rules提示\n7. 修复avatar组件src为空时，展示状态不对\n8. 其他修复\n## 2.0.27（2022-01-28）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1.样式修复\n## 2.0.26（2022-01-28）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1.样式修复\n## 2.0.25（2022-01-27）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复text组件mode=price时，可能会导致精度错误的问题\n2. 添加$u.setConfig()方法，可设置uView内置的config, props, zIndex, color属性，详见：[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)\n3. 优化form组件在errorType=toast时，如果输入错误页面会有抖动的问题\n4. 修复$u.addUnit()对配置默认单位可能无效的问题\n## 2.0.24（2022-01-25）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复swiper在current指定非0时缩放有误\n2. 修复u-icon添加stop属性的时候报错\n3. 优化遗留的通过正则判断rpx单位的问题\n4. 优化Layout布局 vue使用gutter时，会超出固定区域\n5. 优化search组件高度单位问题（rpx -> px）\n6. 修复u-image slot 加载和错误的图片失去了高度\n7. 修复u-index-list中footer插槽与header插槽存在性判断错误\n8. 修复部分机型下u-popup关闭时会闪烁\n9. 修复u-image在nvue-app下失去宽高\n10. 修复u-popup运行报错\n11. 修复u-tooltip报错\n12. 修复box-sizing在app下的警告\n13. 修复u-navbar在小程序中报运行时错误\n14. 其他修复\n## 2.0.23（2022-01-24）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题\n2. 修复col组件gutter参数带rpx单位处理不正确的问题\n3. 修复text组件单行时无法显示省略号的问题\n4. navbar添加titleStyle参数\n5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题\n## 2.0.22（2022-01-19）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. $u.page()方法优化，避免在特殊场景可能报错的问题\n2. picker组件添加immediateChange参数\n3. 新增$u.pages()方法\n## 2.0.21（2022-01-19）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 优化：form组件在用户设置rules的时候提示用户model必传\n2. 优化遗留的通过正则判断rpx单位的问题\n3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后，placeholder高度填充不正确\n4. 修复swiper在current指定非0时缩放有误\n5. 修复u-icon添加stop属性的时候报错\n6. 修复upload组件在accept=all的时候没有作用\n7. 修复在text组件mode为phone时call属性无效的问题\n8. 处理u-form clearValidate方法\n9. 其他修复\n## 2.0.20（2022-01-14）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复calendar默认会选择一个日期，如果直接点确定的话，无法取到值的问题\n2. 修复Slider缺少disabled props 还有注释\n3. 修复u-notice-bar点击事件无法拿到index索引值的问题\n4. 修复u-collapse-item在vue文件下，app端自定义插槽不生效的问题\n5. 优化头像为空时显示默认头像 \n6. 修复图片地址赋值后判断加载状态为完成问题\n7. 修复日历滚动到默认日期月份区域\n8. search组件暴露点击左边icon事件\n9. 修复u-form clearValidate方法不生效\n10. upload h5端增加返回文件参数（文件的name参数）\n11. 处理upload选择文件后url为blob类型无法预览的问题\n12. u-code-input 修复输入框没有往左移出一半屏幕\n13. 修复Upload上传 disabled为true时，控制台报hoverClass类型错误\n14. 临时处理ios app下grid点击坍塌问题\n15. 其他修复\n## 2.0.19（2021-12-29）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 优化微信小程序包体积可在微信中预览，请升级HbuilderX3.3.4，同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”\n2. 优化微信小程序setData性能，处理某些方法如$u.route()无法在模板中使用的问题\n3. navbar添加autoBack参数\n4. 允许avatar组件的事件冒泡\n5. 修复cell组件报错问题\n6. 其他修复\n## 2.0.18（2021-12-28）\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复app端编译报错问题\n2. 重新处理微信小程序端setData过大的性能问题\n3. 修复边框问题\n4. 修复最大最小月份不大于0则没有数据出现的问题\n5. 修复SwipeAction微信小程序端无法上下滑动问题\n6. 修复input的placeholder在小程序端默认显示为true问题\n7. 修复divider组件click事件无效问题\n8. 修复u-code-input maxlength 属性值为 String 类型时显示异常\n9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题\n10. 处理form-item的label为top时，取消错误提示的左边距\n11. 其他修复\n## 2.0.17（2021-12-26）\n## uView正在参与开源中国的“年度最佳项目”评选，之前投过票的现在也可以投票，恳请同学们投一票，[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 解决HBuilderX3.3.3.20211225版本导致的样式问题\n2. calendar日历添加monthNum参数\n3. navbar添加center slot\n## 2.0.16（2021-12-25）\n## uView正在参与开源中国的“年度最佳项目”评选，之前投过票的现在也可以投票，恳请同学们投一票，[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 解决微信小程序setData性能问题\n2. 修复count-down组件change事件不触发问题\n## 2.0.15（2021-12-21）\n## uView正在参与开源中国的“年度最佳项目”评选，之前投过票的现在也可以投票，恳请同学们投一票，[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复Cell单元格titleWidth无效\n2. 修复cheakbox组件ischecked不更新\n3. 修复keyboard是否显示\".\"按键默认值问题\n4. 修复number-keyboard是否显示键盘的\".\"符号问题\n5. 修复Input输入框 readonly无效\n6. 修复u-avatar 导致打包app、H5时候报错问题\n7. 修复Upload上传deletable无效\n8. 修复upload当设置maxSize时无效的问题\n9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题\n10. 修复rate组件在有padding的view内，显示的星星位置和可触摸区域不匹配，无法正常选中星星\n## 2.0.13（2021-12-14）\n## [点击加群交流反馈：364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题\n## 2.0.12（2021-12-14）\n## [点击加群交流反馈：364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复tabs组件在vue环境下划线消失的问题\n2. 修复upload组件在安卓小程序无法选择视频的问题\n3. 添加uni.$u.config.unit配置，用于配置参数默认单位，详见：[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)\n4. 修复textarea组件在没绑定v-model时，字符统计不生效问题\n5. 修复nvue下控制是否出现滚动条失效问题\n## 2.0.11（2021-12-13）\n## [点击加群交流反馈：364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. text组件align参数无效的问题\n2. subsection组件添加keyName参数\n3. upload组件无法判断[Object file]类型的问题\n4. 处理notify层级过低问题\n5. codeInput组件添加disabledDot参数\n6. 处理actionSheet组件round参数无效的问题\n7. calendar组件添加round参数用于控制圆角值\n8. 处理swipeAction组件在vue环境下默认被打开的问题\n9. button组件的throttleTime节流参数无效的问题\n10. 解决u-notify手动关闭方法close()无效的问题\n11. input组件readonly不生效问题\n12. tag组件type参数为info不生效问题\n## 2.0.10（2021-12-08）\n## [点击加群交流反馈：364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复button sendMessagePath属性不生效\n2. 修复DatetimePicker选择器title无效\n3. 修复u-toast设置loading=true不生效\n4. 修复u-text金额模式传0报错\n5. 修复u-toast组件的icon属性配置不生效\n6. button的icon在特殊场景下的颜色优化\n7. IndexList优化，增加#\n## 2.0.9（2021-12-01）\n## [点击加群交流反馈：232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 优化swiper的height支持100%值(仅vue有效)，修复嵌入视频时click事件无法触发的问题\n2. 优化tabs组件对list值为空的判断，或者动态变化list时重新计算相关尺寸的问题\n3. 优化datetime-picker组件逻辑，让其后续打开的默认值为上一次的选中值，需要通过v-model绑定值才有效\n4. 修复upload内嵌在其他组件中，选择图片可能不会换行的问题\n## 2.0.8（2021-12-01）\n## [点击加群交流反馈：232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复toast的position参数无效问题\n2. 处理input在ios nvue上无法获得焦点的问题\n3. avatar-group组件添加extraValue参数，让剩余展示数量可手动控制\n4. tabs组件添加keyName参数用于配置从对象中读取的键名\n5. 处理text组件名字脱敏默认配置无效的问题\n6. 处理picker组件item文本太长换行问题\n## 2.0.7（2021-11-30）\n## [点击加群交流反馈：232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 修复radio和checkbox动态改变v-model无效的问题。\n2. 优化form规则validator在微信小程序用法\n3. 修复backtop组件mode参数在微信小程序无效的问题\n4. 处理Album的previewFullImage属性无效的问题\n5. 处理u-datetime-picker组件mode='time'在选择改变时间时，控制台报错的问题\n## 2.0.6（2021-11-27）\n## [点击加群交流反馈：232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. 处理tag组件在vue下边框无效的问题。\n2. 处理popup组件圆角参数可能无效的问题。\n3. 处理tabs组件lineColor参数可能无效的问题。\n4. propgress组件在值很小时，显示异常的问题。\n## 2.0.5（2021-11-25）\n## [点击加群交流反馈：232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. calendar在vue下显示异常问题。 \n2. form组件labelPosition和errorType参数无效的问题\n3. input组件inputAlign无效的问题\n4. 其他一些修复\n## 2.0.4（2021-11-23）\n## [点击加群交流反馈：232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n0. input组件缺失@confirm事件，以及subfix和prefix无效问题\n1. component.scss文件样式在vue下干扰全局布局问题\n2. 修复subsection在vue环境下表现异常的问题\n3. tag组件的bgColor等参数无效的问题\n4. upload组件不换行的问题\n5. 其他的一些修复处理\n## 2.0.3（2021-11-16）\n## [点击加群交流反馈：1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. uView2.0已实现全面兼容nvue\n2. uView2.0对1.x进行了架构重构，细节和性能都有极大提升\n3. 目前uView2.0为公测阶段，相关细节可能会有变动\n4. 我们写了一份与1.x的对比指南，详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)\n5. 处理modal的confirm回调事件拼写错误问题\n6. 处理input组件@input事件参数错误问题\n7. 其他一些修复\n## 2.0.2（2021-11-16）\n## [点击加群交流反馈：1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. uView2.0已实现全面兼容nvue\n2. uView2.0对1.x进行了架构重构，细节和性能都有极大提升\n3. 目前uView2.0为公测阶段，相关细节可能会有变动\n4. 我们写了一份与1.x的对比指南，详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)\n5. 修复input组件formatter参数缺失问题\n6. 优化loading-icon组件的scss写法问题，防止不兼容新版本scss\n## 2.0.0(2020-11-15)\n## [点击加群交流反馈：1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)\n\n# uView2.0重磅发布，利剑出鞘，一统江湖\n\n1. uView2.0已实现全面兼容nvue\n2. uView2.0对1.x进行了架构重构，细节和性能都有极大提升\n3. 目前uView2.0为公测阶段，相关细节可能会有变动\n4. 我们写了一份与1.x的对比指南，详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)\n5. 修复input组件formatter参数缺失问题\n\n\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u--form/u--form.vue",
    "content": "<template>\n\t<uvForm\n\t\tref=\"uForm\"\n\t\t:model=\"model\"\n\t\t:rules=\"rules\"\n\t\t:errorType=\"errorType\"\n\t\t:borderBottom=\"borderBottom\"\n\t\t:labelPosition=\"labelPosition\"\n\t\t:labelWidth=\"labelWidth\"\n\t\t:labelAlign=\"labelAlign\"\n\t\t:labelStyle=\"labelStyle\"\n\t\t:customStyle=\"customStyle\"\n\t>\n\t\t<slot />\n\t</uvForm>\n</template>\n\n<script>\n\t/**\n\t * 此组件存在的理由是，在nvue下，u-form被uni-app官方占用了，u-form在nvue中相当于form组件\n\t * 所以在nvue下，取名为u--form，内部其实还是u-form.vue，只不过做一层中转\n\t */\n\timport uvForm from '../u-form/u-form.vue';\n\timport props from '../u-form/props.js'\n\texport default {\n\t\t// #ifdef MP-WEIXIN\n\t\tname: 'u-form',\n\t\t// #endif\n\t\t// #ifndef MP-WEIXIN\n\t\tname: 'u--form',\n\t\t// #endif\n\t\tmixins: [uni.$u.mpMixin, props, uni.$u.mixin],\n\t\tcomponents: {\n\t\t\tuvForm\n\t\t},\n\t\tcreated() {\n\t\t\tthis.children = []\n\t\t},\n\t\tmethods: {\n\t\t\t// 手动设置校验的规则，如果规则中有函数的话，微信小程序中会过滤掉，所以只能手动调用设置规则\n\t\t\tsetRules(rules) {\n\t\t\t\tthis.$refs.uForm.setRules(rules)\n\t\t\t},\n\t\t\tvalidate() {\n\t\t\t\t/**\n\t\t\t\t * 在微信小程序中，通过this.$parent拿到的父组件是u--form，而不是其内嵌的u-form\n\t\t\t\t * 导致在u-form组件中，拿不到对应的children数组，从而校验无效，所以这里每次调用u-form组件中的\n\t\t\t\t * 对应方法的时候，在小程序中都先将u--form的children赋值给u-form中的children\n\t\t\t\t */\n\t\t\t\t// #ifdef MP-WEIXIN\n\t\t\t\tthis.setMpData()\n\t\t\t\t// #endif\n\t\t\t\treturn this.$refs.uForm.validate()\n\t\t\t},\n\t\t\tvalidateField(value, callback) {\n\t\t\t\t// #ifdef MP-WEIXIN\n\t\t\t\tthis.setMpData()\n\t\t\t\t// #endif\n\t\t\t\treturn this.$refs.uForm.validateField(value, callback)\n\t\t\t},\n\t\t\tresetFields() {\n\t\t\t\t// #ifdef MP-WEIXIN\n\t\t\t\tthis.setMpData()\n\t\t\t\t// #endif\n\t\t\t\treturn this.$refs.uForm.resetFields()\n\t\t\t},\n\t\t\tclearValidate(props) {\n\t\t\t\t// #ifdef MP-WEIXIN\n\t\t\t\tthis.setMpData()\n\t\t\t\t// #endif\n\t\t\t\treturn this.$refs.uForm.clearValidate(props)\n\t\t\t},\n\t\t\tsetMpData() {\n\t\t\t\tthis.$refs.uForm.children = this.children\n\t\t\t}\n\t\t},\n\t}\n</script>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u--image/u--image.vue",
    "content": "<template>\n\t<uvImage \n\t\t:src=\"src\"\n\t\t:mode=\"mode\"\n\t\t:width=\"width\"\n\t\t:height=\"height\"\n\t\t:shape=\"shape\"\n\t\t:radius=\"radius\"\n\t\t:lazyLoad=\"lazyLoad\"\n\t\t:showMenuByLongpress=\"showMenuByLongpress\"\n\t\t:loadingIcon=\"loadingIcon\"\n\t\t:errorIcon=\"errorIcon\"\n\t\t:showLoading=\"showLoading\"\n\t\t:showError=\"showError\"\n\t\t:fade=\"fade\"\n\t\t:webp=\"webp\"\n\t\t:duration=\"duration\"\n\t\t:bgColor=\"bgColor\"\n\t\t:customStyle=\"customStyle\"\n\t\t@click=\"$emit('click')\"\n\t\t@error=\"$emit('error')\"\n\t\t@load=\"$emit('load')\"\n\t>\n\t\t<template v-slot:loading>\n\t\t\t<slot name=\"loading\"></slot>\n\t\t</template>\n\t\t<template v-slot:error>\n\t\t\t<slot name=\"error\"></slot>\n\t\t</template>\n\t</uvImage>\n</template>\n\n<script>\n\t/**\n\t * 此组件存在的理由是，在nvue下，u-image被uni-app官方占用了，u-image在nvue中相当于image组件\n\t * 所以在nvue下，取名为u--image，内部其实还是u-iamge.vue，只不过做一层中转\n\t */\n\timport uvImage from '../u-image/u-image.vue';\n\timport props from '../u-image/props.js';\n\texport default {\n\t\tname: 'u--image',\n\t\tmixins: [uni.$u.mpMixin, props, uni.$u.mixin],\n\t\tcomponents: {\n\t\t\tuvImage\n\t\t},\n\t}\n</script>"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u--input/u--input.vue",
    "content": "<template>\n\t<uvInput \n\t\t:value=\"value\"\n\t\t:type=\"type\"\n\t\t:fixed=\"fixed\"\n\t\t:disabled=\"disabled\"\n\t\t:disabledColor=\"disabledColor\"\n\t\t:clearable=\"clearable\"\n\t\t:password=\"password\"\n\t\t:maxlength=\"maxlength\"\n\t\t:placeholder=\"placeholder\"\n\t\t:placeholderClass=\"placeholderClass\"\n\t\t:placeholderStyle=\"placeholderStyle\"\n\t\t:showWordLimit=\"showWordLimit\"\n\t\t:confirmType=\"confirmType\"\n\t\t:confirmHold=\"confirmHold\"\n\t\t:holdKeyboard=\"holdKeyboard\"\n\t\t:focus=\"focus\"\n\t\t:autoBlur=\"autoBlur\"\n\t\t:disableDefaultPadding=\"disableDefaultPadding\"\n\t\t:cursor=\"cursor\"\n\t\t:cursorSpacing=\"cursorSpacing\"\n\t\t:selectionStart=\"selectionStart\"\n\t\t:selectionEnd=\"selectionEnd\"\n\t\t:adjustPosition=\"adjustPosition\"\n\t\t:inputAlign=\"inputAlign\"\n\t\t:fontSize=\"fontSize\"\n\t\t:color=\"color\"\n\t\t:prefixIcon=\"prefixIcon\"\n\t\t:suffixIcon=\"suffixIcon\"\n\t\t:suffixIconStyle=\"suffixIconStyle\"\n\t\t:prefixIconStyle=\"prefixIconStyle\"\n\t\t:border=\"border\"\n\t\t:readonly=\"readonly\"\n\t\t:shape=\"shape\"\n\t\t:customStyle=\"customStyle\"\n\t\t:formatter=\"formatter\"\n\t\t:ignoreCompositionEvent=\"ignoreCompositionEvent\"\n\t\t@focus=\"$emit('focus')\"\n\t\t@blur=\"e => $emit('blur', e)\"\n\t\t@keyboardheightchange=\"$emit('keyboardheightchange')\"\n\t\t@change=\"e => $emit('change', e)\"\n\t\t@input=\"e => $emit('input', e)\"\n\t\t@confirm=\"e => $emit('confirm', e)\"\n\t\t@clear=\"$emit('clear')\"\n\t\t@click=\"$emit('click')\"\n\t>\n\t\t<!-- #ifdef MP -->\n\t\t<slot name=\"prefix\"></slot>\n\t\t<slot name=\"suffix\"></slot>\n\t\t<!-- #endif -->\n\t\t<!-- #ifndef MP -->\n\t\t<slot name=\"prefix\" slot=\"prefix\"></slot>\n\t\t<slot name=\"suffix\" slot=\"suffix\"></slot>\n\t\t<!-- #endif -->\n\t</uvInput>\n</template>\n\n<script>\n\t/**\n\t * 此组件存在的理由是，在nvue下，u-input被uni-app官方占用了，u-input在nvue中相当于input组件\n\t * 所以在nvue下，取名为u--input，内部其实还是u-input.vue，只不过做一层中转\n\t */\n\timport uvInput from '../u-input/u-input.vue';\n\timport props from '../u-input/props.js'\n\texport default {\n\t\tname: 'u--input',\n\t\tmixins: [uni.$u.mpMixin, props, uni.$u.mixin],\n\t\tcomponents: {\n\t\t\tuvInput\n\t\t},\n\t}\n</script>"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u--text/u--text.vue",
    "content": "<template>\n    <uvText\n        :type=\"type\"\n        :show=\"show\"\n        :text=\"text\"\n        :prefixIcon=\"prefixIcon\"\n        :suffixIcon=\"suffixIcon\"\n        :mode=\"mode\"\n        :href=\"href\"\n        :format=\"format\"\n        :call=\"call\"\n        :openType=\"openType\"\n        :bold=\"bold\"\n        :block=\"block\"\n        :lines=\"lines\"\n        :color=\"color\"\n\t\t:decoration=\"decoration\"\n        :size=\"size\"\n        :iconStyle=\"iconStyle\"\n        :margin=\"margin\"\n        :lineHeight=\"lineHeight\"\n        :align=\"align\"\n        :wordWrap=\"wordWrap\"\n        :customStyle=\"customStyle\"\n        @click=\"$emit('click')\"\n    ></uvText>\n</template>\n\n<script>\n/**\n * 此组件存在的理由是，在nvue下，u-text被uni-app官方占用了，u-text在nvue中相当于input组件\n * 所以在nvue下，取名为u--input，内部其实还是u-text.vue，只不过做一层中转\n * 不使用v-bind=\"$attrs\"，而是分开独立写传参，是因为微信小程序不支持此写法\n */\nimport uvText from \"../u-text/u-text.vue\";\nimport props from \"../u-text/props.js\";\nexport default {\n    name: \"u--text\",\n    mixins: [uni.$u.mpMixin, props, uni.$u.mixin],\n    components: {\n        uvText,\n    },\n};\n</script>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u--textarea/u--textarea.vue",
    "content": "<template>\n\t<uvTextarea\n\t\t:value=\"value\"\n\t\t:placeholder=\"placeholder\"\n\t\t:height=\"height\"\n\t\t:confirmType=\"confirmType\"\n\t\t:disabled=\"disabled\"\n\t\t:count=\"count\"\n\t\t:focus=\"focus\"\n\t\t:autoHeight=\"autoHeight\"\n\t\t:fixed=\"fixed\"\n\t\t:cursorSpacing=\"cursorSpacing\"\n\t\t:cursor=\"cursor\"\n\t\t:showConfirmBar=\"showConfirmBar\"\n\t\t:selectionStart=\"selectionStart\"\n\t\t:selectionEnd=\"selectionEnd\"\n\t\t:adjustPosition=\"adjustPosition\"\n\t\t:disableDefaultPadding=\"disableDefaultPadding\"\n\t\t:holdKeyboard=\"holdKeyboard\"\n\t\t:maxlength=\"maxlength\"\n\t\t:border=\"border\"\n\t\t:customStyle=\"customStyle\"\n\t\t:formatter=\"formatter\"\n\t\t:ignoreCompositionEvent=\"ignoreCompositionEvent\"\n\t\t@focus=\"e => $emit('focus')\"\n\t\t@blur=\"e => $emit('blur')\"\n\t\t@linechange=\"e => $emit('linechange', e)\"\n\t\t@confirm=\"e => $emit('confirm')\"\n\t\t@input=\"e => $emit('input', e)\"\n\t\t@keyboardheightchange=\"e => $emit('keyboardheightchange')\"\n\t></uvTextarea>\n</template>\n\n<script>\n\t/**\n\t * 此组件存在的理由是，在nvue下，u--textarea被uni-app官方占用了，u-textarea在nvue中相当于textarea组件\n\t * 所以在nvue下，取名为u--textarea，内部其实还是u-textarea.vue，只不过做一层中转\n\t */\n\timport uvTextarea from '../u-textarea/u-textarea.vue';\n\timport props from '../u-textarea/props.js'\n\texport default {\n\t\tname: 'u--textarea',\n\t\tmixins: [uni.$u.mpMixin, props, uni.$u.mixin],\n\t\tcomponents: {\n\t\t\tuvTextarea\n\t\t},\n\t}\n</script>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-action-sheet/props.js",
    "content": "export default {\n    props: {\n        // 操作菜单是否展示 （默认false）\n        show: {\n            type: Boolean,\n            default: uni.$u.props.actionSheet.show\n        },\n        // 标题\n        title: {\n            type: String,\n            default: uni.$u.props.actionSheet.title\n        },\n        // 选项上方的描述信息\n        description: {\n            type: String,\n            default: uni.$u.props.actionSheet.description\n        },\n        // 数据\n        actions: {\n            type: Array,\n            default: uni.$u.props.actionSheet.actions\n        },\n        // 取消按钮的文字，不为空时显示按钮\n        cancelText: {\n            type: String,\n            default: uni.$u.props.actionSheet.cancelText\n        },\n        // 点击某个菜单项时是否关闭弹窗\n        closeOnClickAction: {\n            type: Boolean,\n            default: uni.$u.props.actionSheet.closeOnClickAction\n        },\n        // 处理底部安全区（默认true）\n        safeAreaInsetBottom: {\n            type: Boolean,\n            default: uni.$u.props.actionSheet.safeAreaInsetBottom\n        },\n        // 小程序的打开方式\n        openType: {\n            type: String,\n            default: uni.$u.props.actionSheet.openType\n        },\n        // 点击遮罩是否允许关闭 (默认true)\n        closeOnClickOverlay: {\n            type: Boolean,\n            default: uni.$u.props.actionSheet.closeOnClickOverlay\n        },\n        // 圆角值\n        round: {\n            type: [Boolean, String, Number],\n            default: uni.$u.props.actionSheet.round\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue",
    "content": "\n<template>\n\t<u-popup\n\t    :show=\"show\"\n\t    mode=\"bottom\"\n\t    @close=\"closeHandler\"\n\t    :safeAreaInsetBottom=\"safeAreaInsetBottom\"\n\t    :round=\"round\"\n\t>\n\t\t<view class=\"u-action-sheet\">\n\t\t\t<view\n\t\t\t    class=\"u-action-sheet__header\"\n\t\t\t    v-if=\"title\"\n\t\t\t>\n\t\t\t\t<text class=\"u-action-sheet__header__title u-line-1\">{{title}}</text>\n\t\t\t\t<view\n\t\t\t\t    class=\"u-action-sheet__header__icon-wrap\"\n\t\t\t\t    @tap.stop=\"cancel\"\n\t\t\t\t>\n\t\t\t\t\t<u-icon\n\t\t\t\t\t    name=\"close\"\n\t\t\t\t\t    size=\"17\"\n\t\t\t\t\t    color=\"#c8c9cc\"\n\t\t\t\t\t    bold\n\t\t\t\t\t></u-icon>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<text\n\t\t\t    class=\"u-action-sheet__description\"\n\t\t\t\t:style=\"[{\n\t\t\t\t\tmarginTop: `${title && description ? 0 : '18px'}`\n\t\t\t\t}]\"\n\t\t\t    v-if=\"description\"\n\t\t\t>{{description}}</text>\n\t\t\t<slot>\n\t\t\t\t<u-line v-if=\"description\"></u-line>\n\t\t\t\t<view class=\"u-action-sheet__item-wrap\">\n\t\t\t\t\t<template v-for=\"(item, index) in actions\">\n\t\t\t\t\t\t<!-- #ifdef MP -->\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t    :key=\"index\"\n\t\t\t\t\t\t    class=\"u-reset-button\"\n\t\t\t\t\t\t    :openType=\"item.openType\"\n\t\t\t\t\t\t    @getuserinfo=\"onGetUserInfo\"\n\t\t\t\t\t\t    @contact=\"onContact\"\n\t\t\t\t\t\t    @getphonenumber=\"onGetPhoneNumber\"\n\t\t\t\t\t\t    @error=\"onError\"\n\t\t\t\t\t\t    @launchapp=\"onLaunchApp\"\n\t\t\t\t\t\t    @opensetting=\"onOpenSetting\"\n\t\t\t\t\t\t    :lang=\"lang\"\n\t\t\t\t\t\t    :session-from=\"sessionFrom\"\n\t\t\t\t\t\t    :send-message-title=\"sendMessageTitle\"\n\t\t\t\t\t\t    :send-message-path=\"sendMessagePath\"\n\t\t\t\t\t\t    :send-message-img=\"sendMessageImg\"\n\t\t\t\t\t\t    :show-message-card=\"showMessageCard\"\n\t\t\t\t\t\t    :app-parameter=\"appParameter\"\n\t\t\t\t\t\t    @tap=\"selectHandler(index)\"\n\t\t\t\t\t\t    :hover-class=\"!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<!-- #endif -->\n\t\t\t\t\t\t\t<view\n\t\t\t\t\t\t\t    class=\"u-action-sheet__item-wrap__item\"\n\t\t\t\t\t\t\t    @tap.stop=\"selectHandler(index)\"\n\t\t\t\t\t\t\t    :hover-class=\"!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''\"\n\t\t\t\t\t\t\t    :hover-stay-time=\"150\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<template v-if=\"!item.loading\">\n\t\t\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\t\t    class=\"u-action-sheet__item-wrap__item__name\"\n\t\t\t\t\t\t\t\t\t    :style=\"[itemStyle(index)]\"\n\t\t\t\t\t\t\t\t\t>{{ item.name }}</text>\n\t\t\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\t\t    v-if=\"item.subname\"\n\t\t\t\t\t\t\t\t\t    class=\"u-action-sheet__item-wrap__item__subname\"\n\t\t\t\t\t\t\t\t\t>{{ item.subname }}</text>\n\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t<u-loading-icon\n\t\t\t\t\t\t\t\t    v-else\n\t\t\t\t\t\t\t\t    custom-class=\"van-action-sheet__loading\"\n\t\t\t\t\t\t\t\t    size=\"18\"\n\t\t\t\t\t\t\t\t    mode=\"circle\"\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<!-- #ifdef MP -->\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<!-- #endif -->\n\t\t\t\t\t\t<u-line v-if=\"index !== actions.length - 1\"></u-line>\n\t\t\t\t\t</template>\n\t\t\t\t</view>\n\t\t\t</slot>\n\t\t\t<u-gap\n\t\t\t    bgColor=\"#eaeaec\"\n\t\t\t    height=\"6\"\n\t\t\t    v-if=\"cancelText\"\n\t\t\t></u-gap>\n\t\t\t<view hover-class=\"u-action-sheet--hover\">\n\t\t\t\t<text\n\t\t\t\t    @touchmove.stop.prevent\n\t\t\t\t    :hover-stay-time=\"150\"\n\t\t\t\t    v-if=\"cancelText\"\n\t\t\t\t    class=\"u-action-sheet__cancel-text\"\n\t\t\t\t    @tap=\"cancel\"\n\t\t\t\t>{{cancelText}}</text>\n\t\t\t</view>\n\t\t</view>\n\t</u-popup>\n</template>\n\n<script>\n\timport openType from '../../libs/mixin/openType'\n\timport button from '../../libs/mixin/button'\n\timport props from './props.js';\n\t/**\n\t * ActionSheet 操作菜单\n\t * @description 本组件用于从底部弹出一个操作菜单，供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI，配置更加灵活，所有平台都表现一致。\n\t * @tutorial https://www.uviewui.com/components/actionSheet.html\n\t * \n\t * @property {Boolean}\t\t\tshow\t\t\t\t操作菜单是否展示 （默认 false ）\n\t * @property {String}\t\t\ttitle\t\t\t\t操作菜单标题\n\t * @property {String}\t\t\tdescription\t\t\t选项上方的描述信息\n\t * @property {Array<Object>}\tactions\t\t\t\t按钮的文字数组，见官方文档示例\n\t * @property {String}\t\t\tcancelText\t\t\t取消按钮的提示文字,不为空时显示按钮\n\t * @property {Boolean}\t\t\tcloseOnClickAction\t点击某个菜单项时是否关闭弹窗 （默认 true ）\n\t * @property {Boolean}\t\t\tsafeAreaInsetBottom\t处理底部安全区 （默认 true ）\n\t * @property {String}\t\t\topenType\t\t\t小程序的打开方式 (contact | launchApp | getUserInfo | openSetting ｜getPhoneNumber ｜error )\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t点击遮罩是否允许关闭  (默认 true )\n\t * @property {Number|String}\tround\t\t\t\t圆角值，默认无圆角  (默认 0 )\n\t * @property {String}\t\t\tlang\t\t\t\t指定返回用户信息的语言，zh_CN 简体中文，zh_TW 繁体中文，en 英文\n\t * @property {String}\t\t\tsessionFrom\t\t\t会话来源，openType=\"contact\"时有效\n\t * @property {String}\t\t\tsendMessageTitle\t会话内消息卡片标题，openType=\"contact\"时有效\n\t * @property {String}\t\t\tsendMessagePath\t\t会话内消息卡片点击跳转小程序路径，openType=\"contact\"时有效\n\t * @property {String}\t\t\tsendMessageImg\t\t会话内消息卡片图片，openType=\"contact\"时有效\n\t * @property {Boolean}\t\t\tshowMessageCard\t\t是否显示会话内消息卡片，设置此参数为 true，用户进入客服会话会在右下角显示\"可能要发送的小程序\"提示，用户点击后可以快速发送小程序消息，openType=\"contact\"时有效 （默认 false ）\n\t * @property {String}\t\t\tappParameter\t\t打开 APP 时，向 APP 传递的参数，openType=launchApp 时有效\n\t * \n\t * @event {Function} select\t\t\t点击ActionSheet列表项时触发 \n\t * @event {Function} close\t\t\t点击取消按钮时触发\n\t * @event {Function} getuserinfo\t用户点击该按钮时，会返回获取到的用户信息，回调的 detail 数据与 wx.getUserInfo 返回的一致，openType=\"getUserInfo\"时有效\n\t * @event {Function} contact\t\t客服消息回调，openType=\"contact\"时有效\n\t * @event {Function} getphonenumber\t获取用户手机号回调，openType=\"getPhoneNumber\"时有效\n\t * @event {Function} error\t\t\t当使用开放能力时，发生错误的回调，openType=\"error\"时有效\n\t * @event {Function} launchapp\t\t打开 APP 成功的回调，openType=\"launchApp\"时有效\n\t * @event {Function} opensetting\t在打开授权设置页后回调，openType=\"openSetting\"时有效\n\t * @example <u-action-sheet :actions=\"list\" :title=\"title\" :show=\"show\"></u-action-sheet>\n\t */\n\texport default {\n\t\tname: \"u-action-sheet\",\n\t\t// 一些props参数和methods方法，通过mixin混入，因为其他文件也会用到\n\t\tmixins: [openType, button, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 操作项目的样式\n\t\t\titemStyle() {\n\t\t\t\treturn (index) => {\n\t\t\t\t\tlet style = {};\n\t\t\t\t\tif (this.actions[index].color) style.color = this.actions[index].color\n\t\t\t\t\tif (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize)\n\t\t\t\t\t// 选项被禁用的样式\n\t\t\t\t\tif (this.actions[index].disabled) style.color = '#c0c4cc'\n\t\t\t\t\treturn style;\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tmethods: {\n\t\t\tcloseHandler() {\n\t\t\t\t// 允许点击遮罩关闭时，才发出close事件\n\t\t\t\tif(this.closeOnClickOverlay) {\n\t\t\t\t\tthis.$emit('close')\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 点击取消按钮\n\t\t\tcancel() {\n\t\t\t\tthis.$emit('close')\n\t\t\t},\n\t\t\tselectHandler(index) {\n\t\t\t\tconst item = this.actions[index]\n\t\t\t\tif (item && !item.disabled && !item.loading) {\n\t\t\t\t\tthis.$emit('select', item)\n\t\t\t\t\tif (this.closeOnClickAction) {\n\t\t\t\t\t\tthis.$emit('close')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-action-sheet-reset-button-width:100% !default;\n\t$u-action-sheet-title-font-size: 16px !default;\n\t$u-action-sheet-title-padding: 12px 30px !default;\n\t$u-action-sheet-title-color: $u-main-color !default;\n\t$u-action-sheet-header-icon-wrap-right:15px !default;\n\t$u-action-sheet-header-icon-wrap-top:15px !default;\n\t$u-action-sheet-description-font-size:13px !default;\n\t$u-action-sheet-description-color:14px !default;\n\t$u-action-sheet-description-margin: 18px 15px !default;\n\t$u-action-sheet-item-wrap-item-padding:15px !default;\n\t$u-action-sheet-item-wrap-name-font-size:16px !default;\n\t$u-action-sheet-item-wrap-subname-font-size:13px !default;\n\t$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;\n\t$u-action-sheet-item-wrap-subname-margin-top:10px !default;\n\t$u-action-sheet-cancel-text-font-size:16px !default;\n\t$u-action-sheet-cancel-text-color:$u-content-color !default;\n\t$u-action-sheet-cancel-text-font-size:15px !default;\n\t$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;\n\n\t.u-reset-button {\n\t\twidth: $u-action-sheet-reset-button-width;\n\t}\n\n\t.u-action-sheet {\n\t\ttext-align: center;\n\t\t&__header {\n\t\t\tposition: relative;\n\t\t\tpadding: $u-action-sheet-title-padding;\n\t\t\t&__title {\n\t\t\t\tfont-size: $u-action-sheet-title-font-size;\n\t\t\t\tcolor: $u-action-sheet-title-color;\n\t\t\t\tfont-weight: bold;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\n\t\t\t&__icon-wrap {\n\t\t\t\tposition: absolute;\n\t\t\t\tright: $u-action-sheet-header-icon-wrap-right;\n\t\t\t\ttop: $u-action-sheet-header-icon-wrap-top;\n\t\t\t}\n\t\t}\n\n\t\t&__description {\n\t\t\tfont-size: $u-action-sheet-description-font-size;\n\t\t\tcolor: $u-tips-color;\n\t\t\tmargin: $u-action-sheet-description-margin;\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t&__item-wrap {\n\n\t\t\t&__item {\n\t\t\t\tpadding: $u-action-sheet-item-wrap-item-padding;\n\t\t\t\t@include flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\tflex-direction: column;\n\n\t\t\t\t&__name {\n\t\t\t\t\tfont-size: $u-action-sheet-item-wrap-name-font-size;\n\t\t\t\t\tcolor: $u-main-color;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t}\n\n\t\t\t\t&__subname {\n\t\t\t\t\tfont-size: $u-action-sheet-item-wrap-subname-font-size;\n\t\t\t\t\tcolor: $u-action-sheet-item-wrap-subname-color;\n\t\t\t\t\tmargin-top: $u-action-sheet-item-wrap-subname-margin-top;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&__cancel-text {\n\t\t\tfont-size: $u-action-sheet-cancel-text-font-size;\n\t\t\tcolor: $u-action-sheet-cancel-text-color;\n\t\t\ttext-align: center;\n\t\t\tpadding: $u-action-sheet-cancel-text-font-size;\n\t\t}\n\n\t\t&--hover {\n\t\t\tbackground-color: $u-action-sheet-cancel-text-hover-background-color;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-album/props.js",
    "content": "export default {\n    props: {\n        // 图片地址，Array<String>|Array<Object>形式\n        urls: {\n            type: Array,\n            default: uni.$u.props.album.urls\n        },\n        // 指定从数组的对象元素中读取哪个属性作为图片地址\n        keyName: {\n            type: String,\n            default: uni.$u.props.album.keyName\n        },\n        // 单图时，图片长边的长度\n        singleSize: {\n            type: [String, Number],\n            default: uni.$u.props.album.singleSize\n        },\n        // 多图时，图片边长\n        multipleSize: {\n            type: [String, Number],\n            default: uni.$u.props.album.multipleSize\n        },\n        // 多图时，图片水平和垂直之间的间隔\n        space: {\n            type: [String, Number],\n            default: uni.$u.props.album.space\n        },\n        // 单图时，图片缩放裁剪的模式\n        singleMode: {\n            type: String,\n            default: uni.$u.props.album.singleMode\n        },\n        // 多图时，图片缩放裁剪的模式\n        multipleMode: {\n            type: String,\n            default: uni.$u.props.album.multipleMode\n        },\n        // 最多展示的图片数量，超出时最后一个位置将会显示剩余图片数量\n        maxCount: {\n            type: [String, Number],\n            default: uni.$u.props.album.maxCount\n        },\n        // 是否可以预览图片\n        previewFullImage: {\n            type: Boolean,\n            default: uni.$u.props.album.previewFullImage\n        },\n        // 每行展示图片数量，如设置，singleSize和multipleSize将会无效\n        rowCount: {\n            type: [String, Number],\n            default: uni.$u.props.album.rowCount\n        },\n        // 超出maxCount时是否显示查看更多的提示\n        showMore: {\n            type: Boolean,\n            default: uni.$u.props.album.showMore\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-album/u-album.vue",
    "content": "<template>\n    <view class=\"u-album\">\n        <view\n            class=\"u-album__row\"\n            ref=\"u-album__row\"\n            v-for=\"(arr, index) in showUrls\"\n            :forComputedUse=\"albumWidth\"\n            :key=\"index\"\n        >\n            <view\n                class=\"u-album__row__wrapper\"\n                v-for=\"(item, index1) in arr\"\n                :key=\"index1\"\n                :style=\"[imageStyle(index + 1, index1 + 1)]\"\n                @tap=\"previewFullImage ? onPreviewTap(getSrc(item)) : ''\"\n            >\n                <image\n                    :src=\"getSrc(item)\"\n                    :mode=\"\n                        urls.length === 1\n                            ? imageHeight > 0\n                                ? singleMode\n                                : 'widthFix'\n                            : multipleMode\n                    \"\n                    :style=\"[\n                        {\n                            width: imageWidth,\n                            height: imageHeight\n                        }\n                    ]\"\n                ></image>\n                <view\n                    v-if=\"\n                        showMore &&\n                        urls.length > rowCount * showUrls.length &&\n                        index === showUrls.length - 1 &&\n                        index1 === showUrls[showUrls.length - 1].length - 1\n                    \"\n                    class=\"u-album__row__wrapper__text\"\n                >\n                    <u--text\n                        :text=\"`+${urls.length - maxCount}`\"\n                        color=\"#fff\"\n                        :size=\"multipleSize * 0.3\"\n                        align=\"center\"\n                        customStyle=\"justify-content: center\"\n                    ></u--text>\n                </view>\n            </view>\n        </view>\n    </view>\n</template>\n\n<script>\nimport props from './props.js'\n// #ifdef APP-NVUE\n// 由于weex为阿里的KPI业绩考核的产物，所以不支持百分比单位，这里需要通过dom查询组件的宽度\nconst dom = uni.requireNativePlugin('dom')\n// #endif\n\n/**\n * Album 相册\n * @description 本组件提供一个类似相册的功能，让开发者开发起来更加得心应手。减少重复的模板代码\n * @tutorial https://www.uviewui.com/components/album.html\n *\n * @property {Array}           urls             图片地址列表 Array<String>|Array<Object>形式\n * @property {String}          keyName          指定从数组的对象元素中读取哪个属性作为图片地址\n * @property {String | Number} singleSize       单图时，图片长边的长度  （默认 180 ）\n * @property {String | Number} multipleSize     多图时，图片边长 （默认 70 ）\n * @property {String | Number} space            多图时，图片水平和垂直之间的间隔 （默认 6 ）\n * @property {String}          singleMode       单图时，图片缩放裁剪的模式 （默认 'scaleToFill' ）\n * @property {String}          multipleMode     多图时，图片缩放裁剪的模式 （默认 'aspectFill' ）\n * @property {String | Number} maxCount         取消按钮的提示文字 （默认 9 ）\n * @property {Boolean}         previewFullImage 是否可以预览图片 （默认 true ）\n * @property {String | Number} rowCount         每行展示图片数量，如设置，singleSize和multipleSize将会无效\t（默认 3 ）\n * @property {Boolean}         showMore         超出maxCount时是否显示查看更多的提示 （默认 true ）\n *\n * @event    {Function}        albumWidth       某些特殊的情况下，需要让文字与相册的宽度相等，这里事件的形式对外发送  （回调参数 width ）\n * @example <u-album :urls=\"urls2\" @albumWidth=\"width => albumWidth = width\" multipleSize=\"68\" ></u-album>\n */\nexport default {\n    name: 'u-album',\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n    data() {\n        return {\n            // 单图的宽度\n            singleWidth: 0,\n            // 单图的高度\n            singleHeight: 0,\n            // 单图时，如果无法获取图片的尺寸信息，让图片宽度默认为容器的一定百分比\n            singlePercent: 0.6\n        }\n    },\n    watch: {\n        urls: {\n            immediate: true,\n            handler(newVal) {\n                if (newVal.length === 1) {\n                    this.getImageRect()\n                }\n            }\n        }\n    },\n    computed: {\n        imageStyle() {\n            return (index1, index2) => {\n                const { space, rowCount, multipleSize, urls } = this,\n                    { addUnit, addStyle } = uni.$u,\n                    rowLen = this.showUrls.length,\n                    allLen = this.urls.length\n                const style = {\n                    marginRight: addUnit(space),\n                    marginBottom: addUnit(space)\n                }\n                // 如果为最后一行，则每个图片都无需下边框\n                if (index1 === rowLen) style.marginBottom = 0\n                // 每行的最右边一张和总长度的最后一张无需右边框\n                if (\n                    index2 === rowCount ||\n                    (index1 === rowLen &&\n                        index2 === this.showUrls[index1 - 1].length)\n                )\n                    style.marginRight = 0\n                return style\n            }\n        },\n        // 将数组划分为二维数组\n        showUrls() {\n            const arr = []\n            this.urls.map((item, index) => {\n                // 限制最大展示数量\n                if (index + 1 <= this.maxCount) {\n                    // 计算该元素为第几个素组内\n                    const itemIndex = Math.floor(index / this.rowCount)\n                    // 判断对应的索引是否存在\n                    if (!arr[itemIndex]) {\n                        arr[itemIndex] = []\n                    }\n                    arr[itemIndex].push(item)\n                }\n            })\n            return arr\n        },\n        imageWidth() {\n            return uni.$u.addUnit(\n                this.urls.length === 1 ? this.singleWidth : this.multipleSize\n            )\n        },\n        imageHeight() {\n            return uni.$u.addUnit(\n                this.urls.length === 1 ? this.singleHeight : this.multipleSize\n            )\n        },\n        // 此变量无实际用途，仅仅是为了利用computed特性，让其在urls长度等变化时，重新计算图片的宽度\n        // 因为用户在某些特殊的情况下，需要让文字与相册的宽度相等，所以这里事件的形式对外发送\n        albumWidth() {\n            let width = 0\n            if (this.urls.length === 1) {\n                width = this.singleWidth\n            } else {\n                width =\n                    this.showUrls[0].length * this.multipleSize +\n                    this.space * (this.showUrls[0].length - 1)\n            }\n            this.$emit('albumWidth', width)\n            return width\n        }\n    },\n    methods: {\n        // 预览图片\n        onPreviewTap(url) {\n            const urls = this.urls.map((item) => {\n                return this.getSrc(item)\n            })\n            uni.previewImage({\n                current: url,\n                urls\n            })\n        },\n        // 获取图片的路径\n        getSrc(item) {\n            return uni.$u.test.object(item)\n                ? (this.keyName && item[this.keyName]) || item.src\n                : item\n        },\n        // 单图时，获取图片的尺寸\n        // 在小程序中，需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸\n        // 在没有添加的情况下，让单图宽度默认为盒子的一定宽度(singlePercent)\n        getImageRect() {\n            const src = this.getSrc(this.urls[0])\n            uni.getImageInfo({\n                src,\n                success: (res) => {\n                    // 判断图片横向还是竖向展示方式\n                    const isHorizotal = res.width >= res.height\n                    this.singleWidth = isHorizotal\n                        ? this.singleSize\n                        : (res.width / res.height) * this.singleSize\n                    this.singleHeight = !isHorizotal\n                        ? this.singleSize\n                        : (res.height / res.width) * this.singleWidth\n                },\n                fail: () => {\n                    this.getComponentWidth()\n                }\n            })\n        },\n        // 获取组件的宽度\n        async getComponentWidth() {\n            // 延时一定时间，以获取dom尺寸\n            await uni.$u.sleep(30)\n            // #ifndef APP-NVUE\n            this.$uGetRect('.u-album__row').then((size) => {\n                this.singleWidth = size.width * this.singlePercent\n            })\n            // #endif\n\n            // #ifdef APP-NVUE\n            // 这里ref=\"u-album__row\"所在的标签为通过for循环出来，导致this.$refs['u-album__row']是一个数组\n            const ref = this.$refs['u-album__row'][0]\n            ref &&\n                dom.getComponentRect(ref, (res) => {\n                    this.singleWidth = res.size.width * this.singlePercent\n                })\n            // #endif\n        }\n    }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '../../libs/css/components.scss';\n\n.u-album {\n    @include flex(column);\n\n    &__row {\n        @include flex(row);\n        flex-wrap: wrap;\n\n        &__wrapper {\n            position: relative;\n\n            &__text {\n                position: absolute;\n                top: 0;\n                left: 0;\n                right: 0;\n                bottom: 0;\n                background-color: rgba(0, 0, 0, 0.3);\n                @include flex(row);\n                justify-content: center;\n                align-items: center;\n            }\n        }\n    }\n}\n</style>"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-alert/props.js",
    "content": "export default {\n    props: {\n        // 显示文字\n        title: {\n            type: String,\n            default: uni.$u.props.alert.title\n        },\n        // 主题，success/warning/info/error\n        type: {\n            type: String,\n            default: uni.$u.props.alert.type\n        },\n        // 辅助性文字\n        description: {\n            type: String,\n            default: uni.$u.props.alert.description\n        },\n        // 是否可关闭\n        closable: {\n            type: Boolean,\n            default: uni.$u.props.alert.closable\n        },\n        // 是否显示图标\n        showIcon: {\n            type: Boolean,\n            default: uni.$u.props.alert.showIcon\n        },\n        // 浅或深色调，light-浅色，dark-深色\n        effect: {\n            type: String,\n            default: uni.$u.props.alert.effect\n        },\n        // 文字是否居中\n        center: {\n            type: Boolean,\n            default: uni.$u.props.alert.center\n        },\n        // 字体大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.alert.fontSize\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-alert/u-alert.vue",
    "content": "<template>\n\t<u-transition\n\t    mode=\"fade\"\n\t    :show=\"show\"\n\t>\n\t\t<view\n\t\t    class=\"u-alert\"\n\t\t    :class=\"[`u-alert--${type}--${effect}`]\"\n\t\t    @tap.stop=\"clickHandler\"\n\t\t    :style=\"[$u.addStyle(customStyle)]\"\n\t\t>\n\t\t\t<view\n\t\t\t    class=\"u-alert__icon\"\n\t\t\t    v-if=\"showIcon\"\n\t\t\t>\n\t\t\t\t<u-icon\n\t\t\t\t    :name=\"iconName\"\n\t\t\t\t    size=\"18\"\n\t\t\t\t    :color=\"iconColor\"\n\t\t\t\t></u-icon>\n\t\t\t</view>\n\t\t\t<view\n\t\t\t    class=\"u-alert__content\"\n\t\t\t    :style=\"[{\n\t\t\t\t\tpaddingRight: closable ? '20px' : 0\n\t\t\t\t}]\"\n\t\t\t>\n\t\t\t\t<text\n\t\t\t\t    class=\"u-alert__content__title\"\n\t\t\t\t    v-if=\"title\"\n\t\t\t\t\t:style=\"[{\n\t\t\t\t\t\tfontSize: $u.addUnit(fontSize),\n\t\t\t\t\t\ttextAlign: center ? 'center' : 'left'\n\t\t\t\t\t}]\"\n\t\t\t\t    :class=\"[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]\"\n\t\t\t\t>{{ title }}</text>\n\t\t\t\t<text\n\t\t\t\t    class=\"u-alert__content__desc\"\n\t\t\t\t\tv-if=\"description\"\n\t\t\t\t\t:style=\"[{\n\t\t\t\t\t\tfontSize: $u.addUnit(fontSize),\n\t\t\t\t\t\ttextAlign: center ? 'center' : 'left'\n\t\t\t\t\t}]\"\n\t\t\t\t    :class=\"[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]\"\n\t\t\t\t>{{ description }}</text>\n\t\t\t</view>\n\t\t\t<view\n\t\t\t    class=\"u-alert__close\"\n\t\t\t    v-if=\"closable\"\n\t\t\t    @tap.stop=\"closeHandler\"\n\t\t\t>\n\t\t\t\t<u-icon\n\t\t\t\t    name=\"close\"\n\t\t\t\t    :color=\"iconColor\"\n\t\t\t\t    size=\"15\"\n\t\t\t\t></u-icon>\n\t\t\t</view>\n\t\t</view>\n\t</u-transition>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Alert  警告提示\n\t * @description 警告提示，展现需要关注的信息。\n\t * @tutorial https://www.uviewui.com/components/alertTips.html\n\t * \n\t * @property {String}\t\t\ttitle       显示的文字 \n\t * @property {String}\t\t\ttype        使用预设的颜色  （默认 'warning' ）\n\t * @property {String}\t\t\tdescription 辅助性文字，颜色比title浅一点，字号也小一点，可选  \n\t * @property {Boolean}\t\t\tclosable    关闭按钮(默认为叉号icon图标)  （默认 false ）\n\t * @property {Boolean}\t\t\tshowIcon    是否显示左边的辅助图标   （ 默认 false ）\n\t * @property {String}\t\t\teffect      多图时，图片缩放裁剪的模式  （默认 'light' ）\n\t * @property {Boolean}\t\t\tcenter\t\t文字是否居中  （默认 false ）\n\t * @property {String | Number}\tfontSize    字体大小  （默认 14 ）\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * @event    {Function}        click       点击组件时触发\n\t * @example  <u-alert :title=\"title\"  type = \"warning\" :closable=\"closable\" :description = \"description\"></u-alert>\n\t */\n\texport default {\n\t\tname: 'u-alert',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tshow: true\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\ticonColor() {\n\t\t\t\treturn this.effect === 'light' ? this.type : '#fff'\n\t\t\t},\n\t\t\t// 不同主题对应不同的图标\n\t\t\ticonName() {\n\t\t\t\tswitch (this.type) {\n\t\t\t\t\tcase 'success':\n\t\t\t\t\t\treturn 'checkmark-circle-fill';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'error':\n\t\t\t\t\t\treturn 'close-circle-fill';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'warning':\n\t\t\t\t\t\treturn 'error-circle-fill';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'info':\n\t\t\t\t\t\treturn 'info-circle-fill';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'primary':\n\t\t\t\t\t\treturn 'more-circle-fill';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault: \n\t\t\t\t\t\treturn 'error-circle-fill';\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击内容\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('click')\n\t\t\t},\n\t\t\t// 点击关闭按钮\n\t\t\tcloseHandler() {\n\t\t\t\tthis.show = false\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-alert {\n\t\tposition: relative;\n\t\tbackground-color: $u-primary;\n\t\tpadding: 8px 10px;\n\t\t@include flex(row);\n\t\talign-items: center;\n\t\tborder-top-left-radius: 4px;\n\t\tborder-top-right-radius: 4px;\n\t\tborder-bottom-left-radius: 4px;\n\t\tborder-bottom-right-radius: 4px;\n\n\t\t&--primary--dark {\n\t\t\tbackground-color: $u-primary;\n\t\t}\n\n\t\t&--primary--light {\n\t\t\tbackground-color: #ecf5ff;\n\t\t}\n\n\t\t&--error--dark {\n\t\t\tbackground-color: $u-error;\n\t\t}\n\n\t\t&--error--light {\n\t\t\tbackground-color: #FEF0F0;\n\t\t}\n\n\t\t&--success--dark {\n\t\t\tbackground-color: $u-success;\n\t\t}\n\n\t\t&--success--light {\n\t\t\tbackground-color: #f5fff0;\n\t\t}\n\n\t\t&--warning--dark {\n\t\t\tbackground-color: $u-warning;\n\t\t}\n\n\t\t&--warning--light {\n\t\t\tbackground-color: #FDF6EC;\n\t\t}\n\n\t\t&--info--dark {\n\t\t\tbackground-color: $u-info;\n\t\t}\n\n\t\t&--info--light {\n\t\t\tbackground-color: #f4f4f5;\n\t\t}\n\n\t\t&__icon {\n\t\t\tmargin-right: 5px;\n\t\t}\n\n\t\t&__content {\n\t\t\t@include flex(column);\n\t\t\tflex: 1;\n\n\t\t\t&__title {\n\t\t\t\tcolor: $u-main-color;\n\t\t\t\tfont-size: 14px;\n\t\t\t\tfont-weight: bold;\n\t\t\t\tcolor: #fff;\n\t\t\t\tmargin-bottom: 2px;\n\t\t\t}\n\n\t\t\t&__desc {\n\t\t\t\tcolor: $u-main-color;\n\t\t\t\tfont-size: 14px;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tcolor: #fff;\n\t\t\t}\n\t\t}\n\n\t\t&__title--dark,\n\t\t&__desc--dark {\n\t\t\tcolor: #FFFFFF;\n\t\t}\n\n\t\t&__text--primary--light,\n\t\t&__text--primary--light {\n\t\t\tcolor: $u-primary;\n\t\t}\n\n\t\t&__text--success--light,\n\t\t&__text--success--light {\n\t\t\tcolor: $u-success;\n\t\t}\n\n\t\t&__text--warning--light,\n\t\t&__text--warning--light {\n\t\t\tcolor: $u-warning;\n\t\t}\n\n\t\t&__text--error--light,\n\t\t&__text--error--light {\n\t\t\tcolor: $u-error;\n\t\t}\n\n\t\t&__text--info--light,\n\t\t&__text--info--light {\n\t\t\tcolor: $u-info;\n\t\t}\n\n\t\t&__close {\n\t\t\tposition: absolute;\n\t\t\ttop: 11px;\n\t\t\tright: 10px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-avatar/props.js",
    "content": "export default {\n    props: {\n        // 头像图片路径(不能为相对路径)\n        src: {\n            type: String,\n            default: uni.$u.props.avatar.src\n        },\n        // 头像形状，circle-圆形，square-方形\n        shape: {\n            type: String,\n            default: uni.$u.props.avatar.shape\n        },\n        // 头像尺寸\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.avatar.size\n        },\n        // 裁剪模式\n        mode: {\n            type: String,\n            default: uni.$u.props.avatar.mode\n        },\n        // 显示的文字\n        text: {\n            type: String,\n            default: uni.$u.props.avatar.text\n        },\n        // 背景色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.avatar.bgColor\n        },\n        // 文字颜色\n        color: {\n            type: String,\n            default: uni.$u.props.avatar.color\n        },\n        // 文字大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.avatar.fontSize\n        },\n        // 显示的图标\n        icon: {\n            type: String,\n            default: uni.$u.props.avatar.icon\n        },\n        // 显示小程序头像，只对百度，微信，QQ小程序有效\n        mpAvatar: {\n            type: Boolean,\n            default: uni.$u.props.avatar.mpAvatar\n        },\n        // 是否使用随机背景色\n        randomBgColor: {\n            type: Boolean,\n            default: uni.$u.props.avatar.randomBgColor\n        },\n        // 加载失败的默认头像(组件有内置默认图片)\n        defaultUrl: {\n            type: String,\n            default: uni.$u.props.avatar.defaultUrl\n        },\n        // 如果配置了randomBgColor为true，且配置了此值，则从默认的背景色数组中取出对应索引的颜色值，取值0-19之间\n        colorIndex: {\n            type: [String, Number],\n            // 校验参数规则，索引在0-19之间\n            validator(n) {\n                return uni.$u.test.range(n, [0, 19]) || n === ''\n            },\n            default: uni.$u.props.avatar.colorIndex\n        },\n        // 组件标识符\n        name: {\n            type: String,\n            default: uni.$u.props.avatar.name\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-avatar/u-avatar.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-avatar\"\n\t\t:class=\"[`u-avatar--${shape}`]\"\n\t\t:style=\"[{\n\t\t\tbackgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $u.random(0, 19)] : bgColor) : 'transparent',\n\t\t\twidth: $u.addUnit(size),\n\t\t\theight: $u.addUnit(size),\n\t\t}, $u.addStyle(customStyle)]\"\n\t\t@tap=\"clickHandler\"\n\t>\n\t\t<slot>\n\t\t\t<!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU  -->\n\t\t\t<open-data\n\t\t\t\tv-if=\"mpAvatar && allowMp\"\n\t\t\t\ttype=\"userAvatarUrl\"\n\t\t\t\t:style=\"[{\n\t\t\t\t\twidth: $u.addUnit(size),\n\t\t\t\t\theight: $u.addUnit(size)\n\t\t\t\t}]\"\n\t\t\t/>\n\t\t\t<!-- #endif -->\n\t\t\t<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU  -->\n\t\t\t<template v-if=\"mpAvatar && allowMp\"></template>\n\t\t\t<!-- #endif -->\n\t\t\t<u-icon\n\t\t\t\tv-else-if=\"icon\"\n\t\t\t\t:name=\"icon\"\n\t\t\t\t:size=\"fontSize\"\n\t\t\t\t:color=\"color\"\n\t\t\t></u-icon>\n\t\t\t<u--text\n\t\t\t\tv-else-if=\"text\"\n\t\t\t\t:text=\"text\"\n\t\t\t\t:size=\"fontSize\"\n\t\t\t\t:color=\"color\"\n\t\t\t\talign=\"center\"\n\t\t\t\tcustomStyle=\"justify-content: center\"\n\t\t\t></u--text>\n\t\t\t<image\n\t\t\t\tclass=\"u-avatar__image\"\n\t\t\t\tv-else\n\t\t\t\t:class=\"[`u-avatar__image--${shape}`]\"\n\t\t\t\t:src=\"avatarUrl || defaultUrl\"\n\t\t\t\t:mode=\"mode\"\n\t\t\t\t@error=\"errorHandler\"\n\t\t\t\t:style=\"[{\n\t\t\t\t\twidth: $u.addUnit(size),\n\t\t\t\t\theight: $u.addUnit(size)\n\t\t\t\t}]\"\n\t\t\t></image>\n\t\t</slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\tconst base64Avatar =\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\";\n\t/**\n\t * Avatar  头像\n\t * @description 本组件一般用于展示头像的地方，如个人中心，或者评论列表页的用户头像展示等场所。\n\t * @tutorial https://www.uviewui.com/components/avatar.html\n\t *\n\t * @property {String}\t\t\tsrc\t\t\t\t头像路径，如加载失败，将会显示默认头像(不能为相对路径)\n\t * @property {String}\t\t\tshape\t\t\t头像形状  （ circle (默认) | square）\n\t * @property {String | Number}\tsize\t\t\t头像尺寸，可以为指定字符串(large, default, mini)，或者数值 （默认 40 ）\n\t * @property {String}\t\t\tmode\t\t\t头像图片的裁剪类型，与uni的image组件的mode参数一致，如效果达不到需求，可尝试传widthFix值 （默认 'scaleToFill' ）\n\t * @property {String}\t\t\ttext\t\t\t用文字替代图片，级别优先于src\n\t * @property {String}\t\t\tbgColor\t\t\t背景颜色，一般显示文字时用 （默认 '#c0c4cc' ）\n\t * @property {String}\t\t\tcolor\t\t\t文字颜色 （默认 '#ffffff' ）\n\t * @property {String | Number}\tfontSize\t\t文字大小  （默认 18 ）\n\t * @property {String}\t\t\ticon\t\t\t显示的图标\n\t * @property {Boolean}\t\t\tmpAvatar\t\t显示小程序头像，只对百度，微信，QQ小程序有效  （默认 false ）\n\t * @property {Boolean}\t\t\trandomBgColor\t是否使用随机背景色  （默认 false ）\n\t * @property {String}\t\t\tdefaultUrl\t\t加载失败的默认头像(组件有内置默认图片)\n\t * @property {String | Number}\tcolorIndex\t\t如果配置了randomBgColor为true，且配置了此值，则从默认的背景色数组中取出对应索引的颜色值，取值0-19之间\n\t * @property {String}\t\t\tname\t\t\t组件标识符  （默认 'level' ）\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\n\t *\n\t * @event    {Function}        click       点击组件时触发   index: 用户传递的标识符\n\t * @example  <u-avatar :src=\"src\" mode=\"square\"></u-avatar>\n\t */\n\texport default {\n\t\tname: 'u-avatar',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 如果配置randomBgColor参数为true，在图标或者文字的模式下，会随机从中取出一个颜色值当做背景色\n\t\t\t\tcolors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',\n\t\t\t\t\t'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',\n\t\t\t\t\t'#73d1f1',\n\t\t\t\t\t'#80a7dc'\n\t\t\t\t],\n\t\t\t\tavatarUrl: this.src,\n\t\t\t\tallowMp: false\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t// 监听头像src的变化，赋值给内部的avatarUrl变量，因为图片加载失败时，需要修改图片的src为默认值\n\t\t\t// 而组件内部不能直接修改props的值，所以需要一个中间变量\n\t\t\tsrc: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newVal) {\n\t\t\t\t\tthis.avatarUrl = newVal\n\t\t\t\t\t// 如果没有传src，则主动触发error事件，用于显示默认的头像，否则src为''空字符等的时候，会无内容展示\n\t\t\t\t\tif(!newVal) {\n\t\t\t\t\t\tthis.errorHandler()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\timageStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\treturn style\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 目前只有这几个小程序平台具有open-data标签\n\t\t\t\t// 其他平台可以通过uni.getUserInfo类似接口获取信息，但是需要弹窗授权(首次)，不合符组件逻辑\n\t\t\t\t// 故目前自动获取小程序头像只支持这几个平台\n\t\t\t\t// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU\n\t\t\t\tthis.allowMp = true\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 判断传入的name属性，是否图片路径，只要带有\"/\"均认为是图片形式\n\t\t\tisImg() {\n\t\t\t\treturn this.src.indexOf('/') !== -1\n\t\t\t},\n\t\t\t// 图片加载时失败时触发\n\t\t\terrorHandler() {\n\t\t\t\tthis.avatarUrl = this.defaultUrl || base64Avatar\n\t\t\t},\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('click', this.name)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-avatar {\n\t\t@include flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\n\t\t&--circle {\n\t\t\tborder-radius: 100px;\n\t\t}\n\n\t\t&--square {\n\t\t\tborder-radius: 4px;\n\t\t}\n\n\t\t&__image {\n\t\t\t&--circle {\n\t\t\t\tborder-radius: 100px;\n\t\t\t}\n\n\t\t\t&--square {\n\t\t\t\tborder-radius: 4px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-avatar-group/props.js",
    "content": "export default {\n    props: {\n        // 头像图片组\n        urls: {\n            type: Array,\n            default: uni.$u.props.avatarGroup.urls\n        },\n        // 最多展示的头像数量\n        maxCount: {\n            type: [String, Number],\n            default: uni.$u.props.avatarGroup.maxCount\n        },\n        // 头像形状\n        shape: {\n            type: String,\n            default: uni.$u.props.avatarGroup.shape\n        },\n        // 图片裁剪模式\n        mode: {\n            type: String,\n            default: uni.$u.props.avatarGroup.mode\n        },\n        // 超出maxCount时是否显示查看更多的提示\n        showMore: {\n            type: Boolean,\n            default: uni.$u.props.avatarGroup.showMore\n        },\n        // 头像大小\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.avatarGroup.size\n        },\n        // 指定从数组的对象元素中读取哪个属性作为图片地址\n        keyName: {\n            type: String,\n            default: uni.$u.props.avatarGroup.keyName\n        },\n\t\t// 头像之间的遮挡比例\n        gap: {\n            type: [String, Number],\n            validator(value) {\n                return value >= 0 && value <= 1\n            },\n            default: uni.$u.props.avatarGroup.gap\n        },\n\t\t// 需额外显示的值\n\t\textraValue: {\n\t\t\ttype: [Number, String],\n\t\t\tdefault: uni.$u.props.avatarGroup.extraValue\n\t\t}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue",
    "content": "<template>\n\t<view class=\"u-avatar-group\">\n\t\t<view\n\t\t    class=\"u-avatar-group__item\"\n\t\t    v-for=\"(item, index) in showUrl\"\n\t\t    :key=\"index\"\n\t\t    :style=\"{\n\t\t\t\tmarginLeft: index === 0 ? 0 : $u.addUnit(-size * gap)\n\t\t\t}\"\n\t\t>\n\t\t\t<u-avatar\n\t\t\t    :size=\"size\"\n\t\t\t    :shape=\"shape\"\n\t\t\t    :mode=\"mode\"\n\t\t\t    :src=\"$u.test.object(item) ? keyName && item[keyName] || item.url : item\"\n\t\t\t></u-avatar>\n\t\t\t<view\n\t\t\t    class=\"u-avatar-group__item__show-more\"\n\t\t\t    v-if=\"showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)\"\n\t\t\t\t@tap=\"clickHandler\"\n\t\t\t>\n\t\t\t\t<u--text\n\t\t\t\t    color=\"#ffffff\"\n\t\t\t\t    :size=\"size * 0.4\"\n\t\t\t\t    :text=\"`+${extraValue || urls.length - showUrl.length}`\"\n\t\t\t\t\talign=\"center\"\n\t\t\t\t\tcustomStyle=\"justify-content: center\"\n\t\t\t\t></u--text>\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * AvatarGroup  头像组\n\t * @description 本组件一般用于展示头像的地方，如个人中心，或者评论列表页的用户头像展示等场所。\n\t * @tutorial https://www.uviewui.com/components/avatar.html\n\t * \n\t * @property {Array}           urls     头像图片组 （默认 [] ）\n\t * @property {String | Number} maxCount 最多展示的头像数量 （ 默认 5 ）\n\t * @property {String}          shape    头像形状（ 'circle' (默认) | 'square' ）\n\t * @property {String}          mode     图片裁剪模式（默认 'scaleToFill' ）\n\t * @property {Boolean}         showMore 超出maxCount时是否显示查看更多的提示 （默认 true ）\n\t * @property {String | Number} size      头像大小 （默认 40 ）\n\t * @property {String}          keyName  指定从数组的对象元素中读取哪个属性作为图片地址 \n\t * @property {String | Number} gap      头像之间的遮挡比例（0.4代表遮挡40%）  （默认 0.5 ）\n\t * @property {String | Number} extraValue  需额外显示的值\n\t * @event    {Function}        showMore 头像组更多点击\n\t * @example  <u-avatar-group:urls=\"urls\" size=\"35\" gap=\"0.4\" ></u-avatar-group:urls=>\n\t */\n\texport default {\n\t\tname: 'u-avatar-group',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tshowUrl() {\n\t\t\t\treturn this.urls.slice(0, this.maxCount)\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('showMore')\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-avatar-group {\n\t\t@include flex;\n\n\t\t&__item {\n\t\t\tmargin-left: -10px;\n\t\t\tposition: relative;\n\n\t\t\t&--no-indent {\n\t\t\t\t// 如果你想质疑作者不会使用:first-child，说明你太年轻，因为nvue不支持\n\t\t\t\tmargin-left: 0;\n\t\t\t}\n\n\t\t\t&__show-more {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\tleft: 0;\n\t\t\t\tright: 0;\n\t\t\t\tbackground-color: rgba(0, 0, 0, 0.3);\n\t\t\t\t@include flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\tborder-radius: 100px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-back-top/props.js",
    "content": "export default {\n    props: {\n        // 返回顶部的形状，circle-圆形，square-方形\n        mode: {\n            type: String,\n            default: uni.$u.props.backtop.mode\n        },\n        // 自定义图标\n        icon: {\n            type: String,\n            default: uni.$u.props.backtop.icon\n        },\n        // 提示文字\n        text: {\n            type: String,\n            default: uni.$u.props.backtop.text\n        },\n        // 返回顶部滚动时间\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.backtop.duration\n        },\n        // 滚动距离\n        scrollTop: {\n            type: [String, Number],\n            default: uni.$u.props.backtop.scrollTop\n        },\n        // 距离顶部多少距离显示，单位px\n        top: {\n            type: [String, Number],\n            default: uni.$u.props.backtop.top\n        },\n        // 返回顶部按钮到底部的距离，单位px\n        bottom: {\n            type: [String, Number],\n            default: uni.$u.props.backtop.bottom\n        },\n        // 返回顶部按钮到右边的距离，单位px\n        right: {\n            type: [String, Number],\n            default: uni.$u.props.backtop.right\n        },\n        // 层级\n        zIndex: {\n            type: [String, Number],\n            default: uni.$u.props.backtop.zIndex\n        },\n        // 图标的样式，对象形式\n        iconStyle: {\n            type: Object,\n            default: uni.$u.props.backtop.iconStyle\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-back-top/u-back-top.vue",
    "content": "<template>\n\t<u-transition\n\t    mode=\"fade\"\n\t    :customStyle=\"backTopStyle\"\n\t    :show=\"show\"\n\t>\n\t\t<view\n\t\t    class=\"u-back-top\"\n\t\t\t:style=\"[contentStyle]\"\n\t\t    v-if=\"!$slots.default && !$slots.$default\"\n\t\t\t@click=\"backToTop\"\n\t\t>\n\t\t\t<u-icon\n\t\t\t    :name=\"icon\"\n\t\t\t    :custom-style=\"iconStyle\"\n\t\t\t></u-icon>\n\t\t\t<text\n\t\t\t    v-if=\"text\"\n\t\t\t    class=\"u-back-top__text\"\n\t\t\t>{{text}}</text>\n\t\t</view>\n\t\t<slot v-else />\n\t</u-transition>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst dom = weex.requireModule('dom')\n\t// #endif\n\t/**\n\t * backTop 返回顶部\n\t * @description 本组件一个用于长页面，滑动一定距离后，出现返回顶部按钮，方便快速返回顶部的场景。\n\t * @tutorial https://uviewui.com/components/backTop.html\n\t * \n\t * @property {String}\t\t\tmode  \t\t返回顶部的形状，circle-圆形，square-方形 （默认 'circle' ）\n\t * @property {String} \t\t\ticon \t\t自定义图标 （默认 'arrow-upward' ） 见官方文档示例\n\t * @property {String} \t\t\ttext \t\t提示文字 \n\t * @property {String | Number}  duration\t返回顶部滚动时间 （默认 100）\n\t * @property {String | Number}  scrollTop\t滚动距离 （默认 0 ）\n\t * @property {String | Number}  top  \t\t距离顶部多少距离显示，单位px （默认 400 ）\n\t * @property {String | Number}  bottom  \t返回顶部按钮到底部的距离，单位px （默认 100 ）\n\t * @property {String | Number}  right  \t\t返回顶部按钮到右边的距离，单位px （默认 20 ）\n\t * @property {String | Number}  zIndex \t\t层级   （默认 9 ）\n\t * @property {Object<Object>}  \ticonStyle \t图标的样式，对象形式   （默认 {color: '#909399',fontSize: '19px'}）\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * \n\t * @example <u-back-top :scrollTop=\"scrollTop\"></u-back-top>\n\t */\n\texport default {\n\t\tname: 'u-back-top',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\tbackTopStyle() {\n\t\t\t\t// 动画组件样式\n\t\t\t\tconst style = {\n\t\t\t\t\tbottom: uni.$u.addUnit(this.bottom),\n\t\t\t\t\tright: uni.$u.addUnit(this.right),\n\t\t\t\t\twidth: '40px',\n\t\t\t\t\theight: '40px',\n\t\t\t\t\tposition: 'fixed',\n\t\t\t\t\tzIndex: 10,\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tshow() {\n\t\t\t\treturn uni.$u.getPx(this.scrollTop) > uni.$u.getPx(this.top)\n\t\t\t},\n\t\t\tcontentStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tlet radius = 0\n\t\t\t\t// 是否圆形\n\t\t\t\tif(this.mode === 'circle') {\n\t\t\t\t\tradius = '100px'\n\t\t\t\t} else {\n\t\t\t\t\tradius = '4px'\n\t\t\t\t}\n\t\t\t\t// 为了兼容安卓nvue，只能这么分开写\n\t\t\t\tstyle.borderTopLeftRadius = radius\n\t\t\t\tstyle.borderTopRightRadius = radius\n\t\t\t\tstyle.borderBottomLeftRadius = radius\n\t\t\t\tstyle.borderBottomRightRadius = radius\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tbackToTop() {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tif (!this.$parent.$refs['u-back-top']) {\n\t\t\t\t\tuni.$u.error(`nvue页面需要给页面最外层元素设置\"ref='u-back-top'`)\n\t\t\t\t}\n\t\t\t\tdom.scrollToElement(this.$parent.$refs['u-back-top'], {\n\t\t\t\t\toffset: 0\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t\t\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tuni.pageScrollTo({\n\t\t\t\t\tscrollTop: 0,\n\t\t\t\t\tduration: this.duration\n\t\t\t\t});\n\t\t\t\t// #endif\n\t\t\t\tthis.$emit('click')\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import '../../libs/css/components.scss';\n     $u-back-top-flex:1 !default;\n     $u-back-top-height:100% !default;\n     $u-back-top-background-color:#E1E1E1 !default;\n     $u-back-top-tips-font-size:12px !default;\n\t.u-back-top {\n\t\t@include flex;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tflex:$u-back-top-flex;\n\t\theight: $u-back-top-height;\n\t\tjustify-content: center;\n\t\tbackground-color: $u-back-top-background-color;\n\n\t\t&__tips {\n\t\t\tfont-size:$u-back-top-tips-font-size;\n\t\t\ttransform: scale(0.8);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-badge/props.js",
    "content": "export default {\n    props: {\n        // 是否显示圆点\n        isDot: {\n            type: Boolean,\n            default: uni.$u.props.badge.isDot\n        },\n        // 显示的内容\n        value: {\n            type: [Number, String],\n            default: uni.$u.props.badge.value\n        },\n        // 是否显示\n        show: {\n            type: Boolean,\n            default: uni.$u.props.badge.show\n        },\n        // 最大值，超过最大值会显示 '{max}+'\n        max: {\n            type: [Number, String],\n            default: uni.$u.props.badge.max\n        },\n        // 主题类型，error|warning|success|primary\n        type: {\n            type: String,\n            default: uni.$u.props.badge.type\n        },\n        // 当数值为 0 时，是否展示 Badge\n        showZero: {\n            type: Boolean,\n            default: uni.$u.props.badge.showZero\n        },\n        // 背景颜色，优先级比type高，如设置，type参数会失效\n        bgColor: {\n            type: [String, null],\n            default: uni.$u.props.badge.bgColor\n        },\n        // 字体颜色\n        color: {\n            type: [String, null],\n            default: uni.$u.props.badge.color\n        },\n        // 徽标形状，circle-四角均为圆角，horn-左下角为直角\n        shape: {\n            type: String,\n            default: uni.$u.props.badge.shape\n        },\n        // 设置数字的显示方式，overflow|ellipsis|limit\n        // overflow会根据max字段判断，超出显示`${max}+`\n        // ellipsis会根据max判断，超出显示`${max}...`\n        // limit会依据1000作为判断条件，超出1000，显示`${value/1000}K`，比如2.2k、3.34w，最多保留2位小数\n        numberType: {\n            type: String,\n            default: uni.$u.props.badge.numberType\n        },\n        // 设置badge的位置偏移，格式为 [x, y]，也即设置的为top和right的值，absolute为true时有效\n        offset: {\n            type: Array,\n            default: uni.$u.props.badge.offset\n        },\n        // 是否反转背景和字体颜色\n        inverted: {\n            type: Boolean,\n            default: uni.$u.props.badge.inverted\n        },\n        // 是否绝对定位\n        absolute: {\n            type: Boolean,\n            default: uni.$u.props.badge.absolute\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-badge/u-badge.vue",
    "content": "<template>\n\t<text\n\t\tv-if=\"show && ((Number(value) === 0 ? showZero : true) || isDot)\"\n\t\t:class=\"[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]\"\n\t\t:style=\"[$u.addStyle(customStyle), badgeStyle]\"\n\t\tclass=\"u-badge\"\n\t>{{ isDot ? '' :showValue }}</text>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * badge 徽标数\n\t * @description 该组件一般用于图标右上角显示未读的消息数量，提示用户点击，有圆点和圆包含文字两种形式。\n\t * @tutorial https://uviewui.com/components/badge.html\n\t * \n\t * @property {Boolean} \t\t\tisDot \t\t是否显示圆点 （默认 false ）\n\t * @property {String | Number} \tvalue \t\t显示的内容\n\t * @property {Boolean} \t\t\tshow \t\t是否显示 （默认 true ）\n\t * @property {String | Number} \tmax \t\t最大值，超过最大值会显示 '{max}+'  （默认999）\n\t * @property {String} \t\t\ttype \t\t主题类型，error|warning|success|primary （默认 'error' ）\n\t * @property {Boolean} \t\t\tshowZero\t当数值为 0 时，是否展示 Badge （默认 false ）\n\t * @property {String} \t\t\tbgColor \t背景颜色，优先级比type高，如设置，type参数会失效\n\t * @property {String} \t\t\tcolor \t\t字体颜色 （默认 '#ffffff' ）\n\t * @property {String} \t\t\tshape \t\t徽标形状，circle-四角均为圆角，horn-左下角为直角 （默认 'circle' ）\n\t * @property {String} \t\t\tnumberType\t设置数字的显示方式，overflow|ellipsis|limit  （默认 'overflow' ）\n\t * @property {Array}} \t\t\toffset\t\t设置badge的位置偏移，格式为 [x, y]，也即设置的为top和right的值，absolute为true时有效\n\t * @property {Boolean} \t\t\tinverted\t是否反转背景和字体颜色（默认 false ）\n\t * @property {Boolean} \t\t\tabsolute\t是否绝对定位（默认 false ）\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * @example <u-badge :type=\"type\" :count=\"count\"></u-badge>\n\t */\n\texport default {\n\t\tname: 'u-badge',\n\t\tmixins: [uni.$u.mpMixin, props, uni.$u.mixin],\n\t\tcomputed: {\n\t\t\t// 是否将badge中心与父组件右上角重合\n\t\t\tboxStyle() {\n\t\t\t\tlet style = {};\n\t\t\t\treturn style;\n\t\t\t},\n\t\t\t// 整个组件的样式\n\t\t\tbadgeStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tif(this.color) {\n\t\t\t\t\tstyle.color = this.color\n\t\t\t\t}\n\t\t\t\tif (this.bgColor && !this.inverted) {\n\t\t\t\t\tstyle.backgroundColor = this.bgColor\n\t\t\t\t}\n\t\t\t\tif (this.absolute) {\n\t\t\t\t\tstyle.position = 'absolute'\n\t\t\t\t\t// 如果有设置offset参数\n\t\t\t\t\tif(this.offset.length) {\n\t\t\t\t\t\t// top和right分为为offset的第一个和第二个值，如果没有第二个值，则right等于top\n\t\t\t\t\t\tconst top = this.offset[0]\n\t\t\t\t\t\tconst right = this.offset[1] || top\n\t\t\t\t\t\tstyle.top = uni.$u.addUnit(top)\n\t\t\t\t\t\tstyle.right = uni.$u.addUnit(right)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tshowValue() {\n\t\t\t\tswitch (this.numberType) {\n\t\t\t\t\tcase \"overflow\":\n\t\t\t\t\t\treturn Number(this.value) > Number(this.max) ? this.max + \"+\" : this.value\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"ellipsis\":\n\t\t\t\t\t\treturn Number(this.value) > Number(this.max) ? \"...\" : this.value\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"limit\":\n\t\t\t\t\t\treturn Number(this.value) > 999 ? Number(this.value) >= 9999 ?\n\t\t\t\t\t\t\tMath.floor(this.value / 1e4 * 100) / 100 + \"w\" : Math.floor(this.value /\n\t\t\t\t\t\t\t\t1e3 * 100) / 100 + \"k\" : this.value\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\treturn Number(this.value)\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t$u-badge-primary: $u-primary !default;\n\t$u-badge-error: $u-error !default;\n\t$u-badge-success: $u-success !default;\n\t$u-badge-info: $u-info !default;\n\t$u-badge-warning: $u-warning !default;\n\t$u-badge-dot-radius: 100px !default;\n\t$u-badge-dot-size: 8px !default;\n\t$u-badge-dot-right: 4px !default;\n\t$u-badge-dot-top: 0 !default;\n\t$u-badge-text-font-size: 11px !default;\n\t$u-badge-text-right: 10px !default;\n\t$u-badge-text-padding: 2px 5px !default;\n\t$u-badge-text-align: center !default;\n\t$u-badge-text-color: #FFFFFF !default;\n\n\t.u-badge {\n\t\tborder-top-right-radius: $u-badge-dot-radius;\n\t\tborder-top-left-radius: $u-badge-dot-radius;\n\t\tborder-bottom-left-radius: $u-badge-dot-radius;\n\t\tborder-bottom-right-radius: $u-badge-dot-radius;\n\t\t@include flex;\n\t\tline-height: $u-badge-text-font-size;\n\t\ttext-align: $u-badge-text-align;\n\t\tfont-size: $u-badge-text-font-size;\n\t\tcolor: $u-badge-text-color;\n\n\t\t&--dot {\n\t\t\theight: $u-badge-dot-size;\n\t\t\twidth: $u-badge-dot-size;\n\t\t}\n\t\t\n\t\t&--inverted {\n\t\t\tfont-size: 13px;\n\t\t}\n\t\t\n\t\t&--not-dot {\n\t\t\tpadding: $u-badge-text-padding;\n\t\t}\n\n\t\t&--horn {\n\t\t\tborder-bottom-left-radius: 0;\n\t\t}\n\n\t\t&--primary {\n\t\t\tbackground-color: $u-badge-primary;\n\t\t}\n\t\t\n\t\t&--primary--inverted {\n\t\t\tcolor: $u-badge-primary;\n\t\t}\n\n\t\t&--error {\n\t\t\tbackground-color: $u-badge-error;\n\t\t}\n\t\t\n\t\t&--error--inverted {\n\t\t\tcolor: $u-badge-error;\n\t\t}\n\n\t\t&--success {\n\t\t\tbackground-color: $u-badge-success;\n\t\t}\n\t\t\n\t\t&--success--inverted {\n\t\t\tcolor: $u-badge-success;\n\t\t}\n\n\t\t&--info {\n\t\t\tbackground-color: $u-badge-info;\n\t\t}\n\t\t\n\t\t&--info--inverted {\n\t\t\tcolor: $u-badge-info;\n\t\t}\n\n\t\t&--warning {\n\t\t\tbackground-color: $u-badge-warning;\n\t\t}\n\t\t\n\t\t&--warning--inverted {\n\t\t\tcolor: $u-badge-warning;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-button/nvue.scss",
    "content": "$u-button-active-opacity:0.75 !default;\n$u-button-loading-text-margin-left:4px !default;\n$u-button-text-color: #FFFFFF !default;\n$u-button-text-plain-error-color:$u-error !default;\n$u-button-text-plain-warning-color:$u-warning !default;\n$u-button-text-plain-success-color:$u-success !default;\n$u-button-text-plain-info-color:$u-info !default;\n$u-button-text-plain-primary-color:$u-primary !default;\n.u-button {\n\t&--active {\n\t\topacity: $u-button-active-opacity;\n\t}\n\t\n\t&--active--plain {\n\t\tbackground-color: rgb(217, 217, 217);\n\t}\n\t\n\t&__loading-text {\n\t\tmargin-left:$u-button-loading-text-margin-left;\n\t}\n\t\n\t&__text,\n\t&__loading-text {\n\t\tcolor:$u-button-text-color;\n\t}\n\t\n\t&__text--plain--error {\n\t\tcolor:$u-button-text-plain-error-color;\n\t}\n\t\n\t&__text--plain--warning {\n\t\tcolor:$u-button-text-plain-warning-color;\n\t}\n\t\n\t&__text--plain--success{\n\t\tcolor:$u-button-text-plain-success-color;\n\t}\n\t\n\t&__text--plain--info {\n\t\tcolor:$u-button-text-plain-info-color;\n\t}\n\t\n\t&__text--plain--primary {\n\t\tcolor:$u-button-text-plain-primary-color;\n\t}\n}"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-button/props.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-16 10:04:04\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-16 10:04:24\n * @FilePath     : /u-view2.0/uview-ui/components/u-button/props.js\n */\nexport default {\n    props: {\n        // 是否细边框\n        hairline: {\n            type: Boolean,\n            default: uni.$u.props.button.hairline\n        },\n        // 按钮的预置样式，info，primary，error，warning，success\n        type: {\n            type: String,\n            default: uni.$u.props.button.type\n        },\n        // 按钮尺寸，large，normal，small，mini\n        size: {\n            type: String,\n            default: uni.$u.props.button.size\n        },\n        // 按钮形状，circle（两边为半圆），square（带圆角）\n        shape: {\n            type: String,\n            default: uni.$u.props.button.shape\n        },\n        // 按钮是否镂空\n        plain: {\n            type: Boolean,\n            default: uni.$u.props.button.plain\n        },\n        // 是否禁止状态\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.button.disabled\n        },\n        // 是否加载中\n        loading: {\n            type: Boolean,\n            default: uni.$u.props.button.loading\n        },\n        // 加载中提示文字\n        loadingText: {\n            type: [String, Number],\n            default: uni.$u.props.button.loadingText\n        },\n        // 加载状态图标类型\n        loadingMode: {\n            type: String,\n            default: uni.$u.props.button.loadingMode\n        },\n        // 加载图标大小\n        loadingSize: {\n            type: [String, Number],\n            default: uni.$u.props.button.loadingSize\n        },\n        // 开放能力，具体请看uniapp稳定关于button组件部分说明\n        // https://uniapp.dcloud.io/component/button\n        openType: {\n            type: String,\n            default: uni.$u.props.button.openType\n        },\n        // 用于 <form> 组件，点击分别会触发 <form> 组件的 submit/reset 事件\n        // 取值为submit（提交表单），reset（重置表单）\n        formType: {\n            type: String,\n            default: uni.$u.props.button.formType\n        },\n        // 打开 APP 时，向 APP 传递的参数，open-type=launchApp时有效\n        // 只微信小程序、QQ小程序有效\n        appParameter: {\n            type: String,\n            default: uni.$u.props.button.appParameter\n        },\n        // 指定是否阻止本节点的祖先节点出现点击态，微信小程序有效\n        hoverStopPropagation: {\n            type: Boolean,\n            default: uni.$u.props.button.hoverStopPropagation\n        },\n        // 指定返回用户信息的语言，zh_CN 简体中文，zh_TW 繁体中文，en 英文。只微信小程序有效\n        lang: {\n            type: String,\n            default: uni.$u.props.button.lang\n        },\n        // 会话来源，open-type=\"contact\"时有效。只微信小程序有效\n        sessionFrom: {\n            type: String,\n            default: uni.$u.props.button.sessionFrom\n        },\n        // 会话内消息卡片标题，open-type=\"contact\"时有效\n        // 默认当前标题，只微信小程序有效\n        sendMessageTitle: {\n            type: String,\n            default: uni.$u.props.button.sendMessageTitle\n        },\n        // 会话内消息卡片点击跳转小程序路径，open-type=\"contact\"时有效\n        // 默认当前分享路径，只微信小程序有效\n        sendMessagePath: {\n            type: String,\n            default: uni.$u.props.button.sendMessagePath\n        },\n        // 会话内消息卡片图片，open-type=\"contact\"时有效\n        // 默认当前页面截图，只微信小程序有效\n        sendMessageImg: {\n            type: String,\n            default: uni.$u.props.button.sendMessageImg\n        },\n        // 是否显示会话内消息卡片，设置此参数为 true，用户进入客服会话会在右下角显示\"可能要发送的小程序\"提示，\n        // 用户点击后可以快速发送小程序消息，open-type=\"contact\"时有效\n        showMessageCard: {\n            type: Boolean,\n            default: uni.$u.props.button.showMessageCard\n        },\n        // 额外传参参数，用于小程序的data-xxx属性，通过target.dataset.name获取\n        dataName: {\n            type: String,\n            default: uni.$u.props.button.dataName\n        },\n        // 节流，一定时间内只能触发一次\n        throttleTime: {\n            type: [String, Number],\n            default: uni.$u.props.button.throttleTime\n        },\n        // 按住后多久出现点击态，单位毫秒\n        hoverStartTime: {\n            type: [String, Number],\n            default: uni.$u.props.button.hoverStartTime\n        },\n        // 手指松开后点击态保留时间，单位毫秒\n        hoverStayTime: {\n            type: [String, Number],\n            default: uni.$u.props.button.hoverStayTime\n        },\n        // 按钮文字，之所以通过props传入，是因为slot传入的话\n        // nvue中无法控制文字的样式\n        text: {\n            type: [String, Number],\n            default: uni.$u.props.button.text\n        },\n        // 按钮图标\n        icon: {\n            type: String,\n            default: uni.$u.props.button.icon\n        },\n        // 按钮图标\n        iconColor: {\n            type: String,\n            default: uni.$u.props.button.icon\n        },\n        // 按钮颜色，支持传入linear-gradient渐变色\n        color: {\n            type: String,\n            default: uni.$u.props.button.color\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-button/u-button.vue",
    "content": "<template>\n    <!-- #ifndef APP-NVUE -->\n    <button\n        :hover-start-time=\"Number(hoverStartTime)\"\n        :hover-stay-time=\"Number(hoverStayTime)\"\n        :form-type=\"formType\"\n        :open-type=\"openType\"\n        :app-parameter=\"appParameter\"\n        :hover-stop-propagation=\"hoverStopPropagation\"\n        :send-message-title=\"sendMessageTitle\"\n        :send-message-path=\"sendMessagePath\"\n        :lang=\"lang\"\n        :data-name=\"dataName\"\n        :session-from=\"sessionFrom\"\n        :send-message-img=\"sendMessageImg\"\n        :show-message-card=\"showMessageCard\"\n        @getphonenumber=\"getphonenumber\"\n        @getuserinfo=\"getuserinfo\"\n        @error=\"error\"\n        @opensetting=\"opensetting\"\n        @launchapp=\"launchapp\"\n        :hover-class=\"!disabled && !loading ? 'u-button--active' : ''\"\n        class=\"u-button u-reset-button\"\n        :style=\"[baseColor, $u.addStyle(customStyle)]\"\n        @tap=\"clickHandler\"\n        :class=\"bemClass\"\n    >\n        <template v-if=\"loading\">\n            <u-loading-icon\n                :mode=\"loadingMode\"\n                :size=\"loadingSize * 1.15\"\n                :color=\"loadingColor\"\n            ></u-loading-icon>\n            <text\n                class=\"u-button__loading-text\"\n                :style=\"[{ fontSize: textSize + 'px' }]\"\n                >{{ loadingText || text }}</text\n            >\n        </template>\n        <template v-else>\n            <u-icon\n                v-if=\"icon\"\n                :name=\"icon\"\n                :color=\"iconColorCom\"\n                :size=\"textSize * 1.35\"\n                :customStyle=\"{ marginRight: '2px' }\"\n            ></u-icon>\n            <slot>\n                <text\n                    class=\"u-button__text\"\n                    :style=\"[{ fontSize: textSize + 'px' }]\"\n                    >{{ text }}</text\n                >\n            </slot>\n        </template>\n    </button>\n    <!-- #endif -->\n\n    <!-- #ifdef APP-NVUE -->\n    <view\n        :hover-start-time=\"Number(hoverStartTime)\"\n        :hover-stay-time=\"Number(hoverStayTime)\"\n        class=\"u-button\"\n        :hover-class=\"\n            !disabled && !loading && !color && (plain || type === 'info')\n                ? 'u-button--active--plain'\n                : !disabled && !loading && !plain\n                ? 'u-button--active'\n                : ''\n        \"\n        @tap=\"clickHandler\"\n        :class=\"bemClass\"\n        :style=\"[baseColor, $u.addStyle(customStyle)]\"\n    >\n        <template v-if=\"loading\">\n            <u-loading-icon\n                :mode=\"loadingMode\"\n                :size=\"loadingSize * 1.15\"\n                :color=\"loadingColor\"\n            ></u-loading-icon>\n            <text\n                class=\"u-button__loading-text\"\n                :style=\"[nvueTextStyle]\"\n                :class=\"[plain && `u-button__text--plain--${type}`]\"\n                >{{ loadingText || text }}</text\n            >\n        </template>\n        <template v-else>\n            <u-icon\n                v-if=\"icon\"\n                :name=\"icon\"\n                :color=\"iconColorCom\"\n                :size=\"textSize * 1.35\"\n            ></u-icon>\n            <text\n                class=\"u-button__text\"\n                :style=\"[\n                    {\n                        marginLeft: icon ? '2px' : 0,\n                    },\n                    nvueTextStyle,\n                ]\"\n                :class=\"[plain && `u-button__text--plain--${type}`]\"\n                >{{ text }}</text\n            >\n        </template>\n    </view>\n    <!-- #endif -->\n</template>\n\n<script>\nimport button from \"../../libs/mixin/button.js\";\nimport openType from \"../../libs/mixin/openType.js\";\nimport props from \"./props.js\";\n/**\n * button 按钮\n * @description Button 按钮\n * @tutorial https://www.uviewui.com/components/button.html\n *\n * @property {Boolean}\t\t\thairline\t\t\t\t是否显示按钮的细边框 (默认 true )\n * @property {String}\t\t\ttype\t\t\t\t\t按钮的预置样式，info，primary，error，warning，success (默认 'info' )\n * @property {String}\t\t\tsize\t\t\t\t\t按钮尺寸，large，normal，mini （默认 normal）\n * @property {String}\t\t\tshape\t\t\t\t\t按钮形状，circle（两边为半圆），square（带圆角） （默认 'square' ）\n * @property {Boolean}\t\t\tplain\t\t\t\t\t按钮是否镂空，背景色透明 （默认 false）\n * @property {Boolean}\t\t\tdisabled\t\t\t\t是否禁用 （默认 false）\n * @property {Boolean}\t\t\tloading\t\t\t\t\t按钮名称前是否带 loading 图标(App-nvue 平台，在 ios 上为雪花，Android上为圆圈) （默认 false）\n * @property {String | Number}\tloadingText\t\t\t\t加载中提示文字\n * @property {String}\t\t\tloadingMode\t\t\t\t加载状态图标类型 （默认 'spinner' ）\n * @property {String | Number}\tloadingSize\t\t\t\t加载图标大小 （默认 15 ）\n * @property {String}\t\t\topenType\t\t\t\t开放能力，具体请看uniapp稳定关于button组件部分说明\n * @property {String}\t\t\tformType\t\t\t\t用于 <form> 组件，点击分别会触发 <form> 组件的 submit/reset 事件\n * @property {String}\t\t\tappParameter\t\t\t打开 APP 时，向 APP 传递的参数，open-type=launchApp时有效 （注：只微信小程序、QQ小程序有效）\n * @property {Boolean}\t\t\thoverStopPropagation\t指定是否阻止本节点的祖先节点出现点击态，微信小程序有效（默认 true ）\n * @property {String}\t\t\tlang\t\t\t\t\t指定返回用户信息的语言，zh_CN 简体中文，zh_TW 繁体中文，en 英文（默认 en ）\n * @property {String}\t\t\tsessionFrom\t\t\t\t会话来源，openType=\"contact\"时有效\n * @property {String}\t\t\tsendMessageTitle\t\t会话内消息卡片标题，openType=\"contact\"时有效\n * @property {String}\t\t\tsendMessagePath\t\t\t会话内消息卡片点击跳转小程序路径，openType=\"contact\"时有效\n * @property {String}\t\t\tsendMessageImg\t\t\t会话内消息卡片图片，openType=\"contact\"时有效\n * @property {Boolean}\t\t\tshowMessageCard\t\t\t是否显示会话内消息卡片，设置此参数为 true，用户进入客服会话会在右下角显示\"可能要发送的小程序\"提示，用户点击后可以快速发送小程序消息，openType=\"contact\"时有效（默认false）\n * @property {String}\t\t\tdataName\t\t\t\t额外传参参数，用于小程序的data-xxx属性，通过target.dataset.name获取\n * @property {String | Number}\tthrottleTime\t\t\t节流，一定时间内只能触发一次 （默认 0 )\n * @property {String | Number}\thoverStartTime\t\t\t按住后多久出现点击态，单位毫秒 （默认 0 )\n * @property {String | Number}\thoverStayTime\t\t\t手指松开后点击态保留时间，单位毫秒 （默认 200 )\n * @property {String | Number}\ttext\t\t\t\t\t按钮文字，之所以通过props传入，是因为slot传入的话（注：nvue中无法控制文字的样式）\n * @property {String}\t\t\ticon\t\t\t\t\t按钮图标\n * @property {String}\t\t\ticonColor\t\t\t\t按钮图标颜色\n * @property {String}\t\t\tcolor\t\t\t\t\t按钮颜色，支持传入linear-gradient渐变色\n * @property {Object}\t\t\tcustomStyle\t\t\t\t定义需要用到的外部样式\n *\n * @event {Function}\tclick\t\t\t非禁止并且非加载中，才能点击\n * @event {Function}\tgetphonenumber\topen-type=\"getPhoneNumber\"时有效\n * @event {Function}\tgetuserinfo\t\t用户点击该按钮时，会返回获取到的用户信息，从返回参数的detail中获取到的值同uni.getUserInfo\n * @event {Function}\terror\t\t\t当使用开放能力时，发生错误的回调\n * @event {Function}\topensetting\t\t在打开授权设置页并关闭后回调\n * @event {Function}\tlaunchapp\t\t打开 APP 成功的回调\n * @example <u-button>月落</u-button>\n */\nexport default {\n    name: \"u-button\",\n    // #ifdef MP\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props],\n    // #endif\n    // #ifndef MP\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n    // #endif\n    data() {\n        return {};\n    },\n    computed: {\n        // 生成bem风格的类名\n        bemClass() {\n            // this.bem为一个computed变量，在mixin中\n            if (!this.color) {\n                return this.bem(\n                    \"button\",\n                    [\"type\", \"shape\", \"size\"],\n                    [\"disabled\", \"plain\", \"hairline\"]\n                );\n            } else {\n                // 由于nvue的原因，在有color参数时，不需要传入type，否则会生成type相关的类型，影响最终的样式\n                return this.bem(\n                    \"button\",\n                    [\"shape\", \"size\"],\n                    [\"disabled\", \"plain\", \"hairline\"]\n                );\n            }\n        },\n        loadingColor() {\n            if (this.plain) {\n                // 如果有设置color值，则用color值，否则使用type主题颜色\n                return this.color\n                    ? this.color\n                    : uni.$u.config.color[`u-${this.type}`];\n            }\n            if (this.type === \"info\") {\n                return \"#c9c9c9\";\n            }\n            return \"rgb(200, 200, 200)\";\n        },\n        iconColorCom() {\n            // 如果是镂空状态，设置了color就用color值，否则使用主题颜色，\n            // u-icon的color能接受一个主题颜色的值\n\t\t\tif (this.iconColor) return this.iconColor;\n\t\t\tif (this.plain) {\n                return this.color ? this.color : this.type;\n            } else {\n                return this.type === \"info\" ? \"#000000\" : \"#ffffff\";\n            }\n        },\n        baseColor() {\n            let style = {};\n            if (this.color) {\n                // 针对自定义了color颜色的情况，镂空状态下，就是用自定义的颜色\n                style.color = this.plain ? this.color : \"white\";\n                if (!this.plain) {\n                    // 非镂空，背景色使用自定义的颜色\n                    style[\"background-color\"] = this.color;\n                }\n                if (this.color.indexOf(\"gradient\") !== -1) {\n                    // 如果自定义的颜色为渐变色，不显示边框，以及通过backgroundImage设置渐变色\n                    // weex文档说明可以写borderWidth的形式，为什么这里需要分开写？\n                    // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西，所以需要这么写才有效\n                    style.borderTopWidth = 0;\n                    style.borderRightWidth = 0;\n                    style.borderBottomWidth = 0;\n                    style.borderLeftWidth = 0;\n                    if (!this.plain) {\n                        style.backgroundImage = this.color;\n                    }\n                } else {\n                    // 非渐变色，则设置边框相关的属性\n                    style.borderColor = this.color;\n                    style.borderWidth = \"1px\";\n                    style.borderStyle = \"solid\";\n                }\n            }\n            return style;\n        },\n        // nvue版本按钮的字体不会继承父组件的颜色，需要对每一个text组件进行单独的设置\n        nvueTextStyle() {\n            let style = {};\n            // 针对自定义了color颜色的情况，镂空状态下，就是用自定义的颜色\n            if (this.type === \"info\") {\n                style.color = \"#323233\";\n            }\n            if (this.color) {\n                style.color = this.plain ? this.color : \"white\";\n            }\n            style.fontSize = this.textSize + \"px\";\n            return style;\n        },\n        // 字体大小\n        textSize() {\n            let fontSize = 14,\n                { size } = this;\n            if (size === \"large\") fontSize = 16;\n            if (size === \"normal\") fontSize = 14;\n            if (size === \"small\") fontSize = 12;\n            if (size === \"mini\") fontSize = 10;\n            return fontSize;\n        },\n    },\n    methods: {\n        clickHandler() {\n            // 非禁止并且非加载中，才能点击\n            if (!this.disabled && !this.loading) {\n\t\t\t\t// 进行节流控制，每this.throttle毫秒内，只在开始处执行\n\t\t\t\tuni.$u.throttle(() => {\n\t\t\t\t\tthis.$emit(\"click\");\n\t\t\t\t}, this.throttleTime);\n            }\n        },\n        // 下面为对接uniapp官方按钮开放能力事件回调的对接\n        getphonenumber(res) {\n            this.$emit(\"getphonenumber\", res);\n        },\n        getuserinfo(res) {\n            this.$emit(\"getuserinfo\", res);\n        },\n        error(res) {\n            this.$emit(\"error\", res);\n        },\n        opensetting(res) {\n            this.$emit(\"opensetting\", res);\n        },\n        launchapp(res) {\n            this.$emit(\"launchapp\", res);\n        },\n    },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n\n/* #ifndef APP-NVUE */\n@import \"./vue.scss\";\n/* #endif */\n\n/* #ifdef APP-NVUE */\n@import \"./nvue.scss\";\n/* #endif */\n\n$u-button-u-button-height: 40px !default;\n$u-button-text-font-size: 15px !default;\n$u-button-loading-text-font-size: 15px !default;\n$u-button-loading-text-margin-left: 4px !default;\n$u-button-large-width: 100% !default;\n$u-button-large-height: 50px !default;\n$u-button-normal-padding: 0 12px !default;\n$u-button-large-padding: 0 15px !default;\n$u-button-normal-font-size: 14px !default;\n$u-button-small-min-width: 60px !default;\n$u-button-small-height: 30px !default;\n$u-button-small-padding: 0px 8px !default;\n$u-button-mini-padding: 0px 8px !default;\n$u-button-small-font-size: 12px !default;\n$u-button-mini-height: 22px !default;\n$u-button-mini-font-size: 10px !default;\n$u-button-mini-min-width: 50px !default;\n$u-button-disabled-opacity: 0.5 !default;\n$u-button-info-color: #323233 !default;\n$u-button-info-background-color: #fff !default;\n$u-button-info-border-color: #ebedf0 !default;\n$u-button-info-border-width: 1px !default;\n$u-button-info-border-style: solid !default;\n$u-button-success-color: #fff !default;\n$u-button-success-background-color: $u-success !default;\n$u-button-success-border-color: $u-button-success-background-color !default;\n$u-button-success-border-width: 1px !default;\n$u-button-success-border-style: solid !default;\n$u-button-primary-color: #fff !default;\n$u-button-primary-background-color: $u-primary !default;\n$u-button-primary-border-color: $u-button-primary-background-color !default;\n$u-button-primary-border-width: 1px !default;\n$u-button-primary-border-style: solid !default;\n$u-button-error-color: #fff !default;\n$u-button-error-background-color: $u-error !default;\n$u-button-error-border-color: $u-button-error-background-color !default;\n$u-button-error-border-width: 1px !default;\n$u-button-error-border-style: solid !default;\n$u-button-warning-color: #fff !default;\n$u-button-warning-background-color: $u-warning !default;\n$u-button-warning-border-color: $u-button-warning-background-color !default;\n$u-button-warning-border-width: 1px !default;\n$u-button-warning-border-style: solid !default;\n$u-button-block-width: 100% !default;\n$u-button-circle-border-top-right-radius: 100px !default;\n$u-button-circle-border-top-left-radius: 100px !default;\n$u-button-circle-border-bottom-left-radius: 100px !default;\n$u-button-circle-border-bottom-right-radius: 100px !default;\n$u-button-square-border-top-right-radius: 3px !default;\n$u-button-square-border-top-left-radius: 3px !default;\n$u-button-square-border-bottom-left-radius: 3px !default;\n$u-button-square-border-bottom-right-radius: 3px !default;\n$u-button-icon-min-width: 1em !default;\n$u-button-plain-background-color: #fff !default;\n$u-button-hairline-border-width: 0.5px !default;\n\n.u-button {\n    height: $u-button-u-button-height;\n    position: relative;\n    align-items: center;\n    justify-content: center;\n    @include flex;\n    /* #ifndef APP-NVUE */\n    box-sizing: border-box;\n    /* #endif */\n    flex-direction: row;\n\n    &__text {\n        font-size: $u-button-text-font-size;\n    }\n\n    &__loading-text {\n        font-size: $u-button-loading-text-font-size;\n        margin-left: $u-button-loading-text-margin-left;\n    }\n\n    &--large {\n        /* #ifndef APP-NVUE */\n        width: $u-button-large-width;\n        /* #endif */\n        height: $u-button-large-height;\n        padding: $u-button-large-padding;\n    }\n\n    &--normal {\n        padding: $u-button-normal-padding;\n        font-size: $u-button-normal-font-size;\n    }\n\n    &--small {\n        /* #ifndef APP-NVUE */\n        min-width: $u-button-small-min-width;\n        /* #endif */\n        height: $u-button-small-height;\n        padding: $u-button-small-padding;\n        font-size: $u-button-small-font-size;\n    }\n\n    &--mini {\n        height: $u-button-mini-height;\n        font-size: $u-button-mini-font-size;\n        /* #ifndef APP-NVUE */\n        min-width: $u-button-mini-min-width;\n        /* #endif */\n        padding: $u-button-mini-padding;\n    }\n\n    &--disabled {\n        opacity: $u-button-disabled-opacity;\n    }\n\n    &--info {\n        color: $u-button-info-color;\n        background-color: $u-button-info-background-color;\n        border-color: $u-button-info-border-color;\n        border-width: $u-button-info-border-width;\n        border-style: $u-button-info-border-style;\n    }\n\n    &--success {\n        color: $u-button-success-color;\n        background-color: $u-button-success-background-color;\n        border-color: $u-button-success-border-color;\n        border-width: $u-button-success-border-width;\n        border-style: $u-button-success-border-style;\n    }\n\n    &--primary {\n        color: $u-button-primary-color;\n        background-color: $u-button-primary-background-color;\n        border-color: $u-button-primary-border-color;\n        border-width: $u-button-primary-border-width;\n        border-style: $u-button-primary-border-style;\n    }\n\n    &--error {\n        color: $u-button-error-color;\n        background-color: $u-button-error-background-color;\n        border-color: $u-button-error-border-color;\n        border-width: $u-button-error-border-width;\n        border-style: $u-button-error-border-style;\n    }\n\n    &--warning {\n        color: $u-button-warning-color;\n        background-color: $u-button-warning-background-color;\n        border-color: $u-button-warning-border-color;\n        border-width: $u-button-warning-border-width;\n        border-style: $u-button-warning-border-style;\n    }\n\n    &--block {\n        @include flex;\n        width: $u-button-block-width;\n    }\n\n    &--circle {\n        border-top-right-radius: $u-button-circle-border-top-right-radius;\n        border-top-left-radius: $u-button-circle-border-top-left-radius;\n        border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;\n        border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;\n    }\n\n    &--square {\n        border-bottom-left-radius: $u-button-square-border-top-right-radius;\n        border-bottom-right-radius: $u-button-square-border-top-left-radius;\n        border-top-left-radius: $u-button-square-border-bottom-left-radius;\n        border-top-right-radius: $u-button-square-border-bottom-right-radius;\n    }\n\n    &__icon {\n        /* #ifndef APP-NVUE */\n        min-width: $u-button-icon-min-width;\n        line-height: inherit !important;\n        vertical-align: top;\n        /* #endif */\n    }\n\n    &--plain {\n        background-color: $u-button-plain-background-color;\n    }\n\n    &--hairline {\n        border-width: $u-button-hairline-border-width !important;\n    }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-button/vue.scss",
    "content": "// nvue下hover-class无效\n$u-button-before-top:50% !default;\n$u-button-before-left:50% !default;\n$u-button-before-width:100% !default;\n$u-button-before-height:100% !default;\n$u-button-before-transform:translate(-50%, -50%) !default;\n$u-button-before-opacity:0 !default;\n$u-button-before-background-color:#000 !default;\n$u-button-before-border-color:#000 !default;\n$u-button-active-before-opacity:.15 !default;\n$u-button-icon-margin-left:4px !default;\n$u-button-plain-u-button-info-color:$u-info;\n$u-button-plain-u-button-success-color:$u-success;\n$u-button-plain-u-button-error-color:$u-error;\n$u-button-plain-u-button-warning-color:$u-error;\n\n.u-button {\n\twidth: 100%;\n\t\n\t&__text {\n\t\twhite-space: nowrap;\n\t\tline-height: 1;\n\t}\n\t\n\t&:before {\n\t\tposition: absolute;\n\t\ttop:$u-button-before-top;\n\t\tleft:$u-button-before-left;\n\t\twidth:$u-button-before-width;\n\t\theight:$u-button-before-height;\n\t\tborder: inherit;\n\t\tborder-radius: inherit;\n\t\ttransform:$u-button-before-transform;\n\t\topacity:$u-button-before-opacity;\n\t\tcontent: \" \";\n\t\tbackground-color:$u-button-before-background-color;\n\t\tborder-color:$u-button-before-border-color;\n\t}\n\t\n\t&--active {\n\t\t&:before {\n\t\t\topacity: .15\n\t\t}\n\t}\n\t\n\t&__icon+&__text:not(:empty),\n\t&__loading-text {\n\t\tmargin-left:$u-button-icon-margin-left;\n\t}\n\t\n\t&--plain {\n\t\t&.u-button--primary {\n\t\t\tcolor: $u-primary;\n\t\t}\n\t}\n\t\n\t&--plain {\n\t\t&.u-button--info {\n\t\t\tcolor:$u-button-plain-u-button-info-color;\n\t\t}\n\t}\n\t\n\t&--plain {\n\t\t&.u-button--success {\n\t\t\tcolor:$u-button-plain-u-button-success-color;\n\t\t}\n\t}\n\t\n\t&--plain {\n\t\t&.u-button--error {\n\t\t\tcolor:$u-button-plain-u-button-error-color;\n\t\t}\n\t}\n\t\n\t&--plain {\n\t\t&.u-button--warning {\n\t\t\tcolor:$u-button-plain-u-button-warning-color;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-calendar/header.vue",
    "content": "<template>\n\t<view class=\"u-calendar-header u-border-bottom\">\n\t\t<text\n\t\t\tclass=\"u-calendar-header__title\"\n\t\t\tv-if=\"showTitle\"\n\t\t>{{ title }}</text>\n\t\t<text\n\t\t\tclass=\"u-calendar-header__subtitle\"\n\t\t\tv-if=\"showSubtitle\"\n\t\t>{{ subtitle }}</text>\n\t\t<view class=\"u-calendar-header__weekdays\">\n\t\t\t<text class=\"u-calendar-header__weekdays__weekday\">一</text>\n\t\t\t<text class=\"u-calendar-header__weekdays__weekday\">二</text>\n\t\t\t<text class=\"u-calendar-header__weekdays__weekday\">三</text>\n\t\t\t<text class=\"u-calendar-header__weekdays__weekday\">四</text>\n\t\t\t<text class=\"u-calendar-header__weekdays__weekday\">五</text>\n\t\t\t<text class=\"u-calendar-header__weekdays__weekday\">六</text>\n\t\t\t<text class=\"u-calendar-header__weekdays__weekday\">日</text>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tname: 'u-calendar-header',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin],\n\t\tprops: {\n\t\t\t// 标题\n\t\t\ttitle: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\t// 副标题\n\t\t\tsubtitle: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\t// 是否显示标题\n\t\t\tshowTitle: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\t// 是否显示副标题\n\t\t\tshowSubtitle: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tname() {\n\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-calendar-header {\n\t\tpadding-bottom: 4px;\n\n\t\t&__title {\n\t\t\tfont-size: 16px;\n\t\t\tcolor: $u-main-color;\n\t\t\ttext-align: center;\n\t\t\theight: 42px;\n\t\t\tline-height: 42px;\n\t\t\tfont-weight: bold;\n\t\t}\n\n\t\t&__subtitle {\n\t\t\tfont-size: 14px;\n\t\t\tcolor: $u-main-color;\n\t\t\theight: 40px;\n\t\t\ttext-align: center;\n\t\t\tline-height: 40px;\n\t\t\tfont-weight: bold;\n\t\t}\n\n\t\t&__weekdays {\n\t\t\t@include flex;\n\t\t\tjustify-content: space-between;\n\n\t\t\t&__weekday {\n\t\t\t\tfont-size: 13px;\n\t\t\t\tcolor: $u-main-color;\n\t\t\t\tline-height: 30px;\n\t\t\t\tflex: 1;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-calendar/month.vue",
    "content": "<template>\n\t<view class=\"u-calendar-month-wrapper\" ref=\"u-calendar-month-wrapper\">\n\t\t<view v-for=\"(item, index) in months\" :key=\"index\" :class=\"[`u-calendar-month-${index}`]\"\n\t\t\t:ref=\"`u-calendar-month-${index}`\" :id=\"`month-${index}`\">\n\t\t\t<text v-if=\"index !== 0\" class=\"u-calendar-month__title\">{{ item.year }}年{{ item.month }}月</text>\n\t\t\t<view class=\"u-calendar-month__days\">\n\t\t\t\t<view v-if=\"showMark\" class=\"u-calendar-month__days__month-mark-wrapper\">\n\t\t\t\t\t<text class=\"u-calendar-month__days__month-mark-wrapper__text\">{{ item.month }}</text>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"u-calendar-month__days__day\" v-for=\"(item1, index1) in item.date\" :key=\"index1\"\n\t\t\t\t\t:style=\"[dayStyle(index, index1, item1)]\" @tap=\"clickHandler(index, index1, item1)\"\n\t\t\t\t\t:class=\"[item1.selected && 'u-calendar-month__days__day__select--selected']\">\n\t\t\t\t\t<view class=\"u-calendar-month__days__day__select\" :style=\"[daySelectStyle(index, index1, item1)]\">\n\t\t\t\t\t\t<text class=\"u-calendar-month__days__day__select__info\"\n\t\t\t\t\t\t\t:class=\"[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']\"\n\t\t\t\t\t\t\t:style=\"[textStyle(item1)]\">{{ item1.day }}</text>\n\t\t\t\t\t\t<text v-if=\"getBottomInfo(index, index1, item1)\"\n\t\t\t\t\t\t\tclass=\"u-calendar-month__days__day__select__buttom-info\"\n\t\t\t\t\t\t\t:class=\"[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']\"\n\t\t\t\t\t\t\t:style=\"[textStyle(item1)]\">{{ getBottomInfo(index, index1, item1) }}</text>\n\t\t\t\t\t\t<text v-if=\"item1.dot\" class=\"u-calendar-month__days__day__select__dot\"></text>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\t// #ifdef APP-NVUE\n\t// 由于nvue不支持百分比单位，需要查询宽度来计算每个日期的宽度\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\timport dayjs from '../../libs/util/dayjs.js';\n\texport default {\n\t\tname: 'u-calendar-month',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin],\n\t\tprops: {\n\t\t\t// 是否显示月份背景色\n\t\t\tshowMark: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\t// 主题色，对底部按钮和选中日期有效\n\t\t\tcolor: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: '#3c9cff'\n\t\t\t},\n\t\t\t// 月份数据\n\t\t\tmonths: {\n\t\t\t\ttype: Array,\n\t\t\t\tdefault: () => []\n\t\t\t},\n\t\t\t// 日期选择类型\n\t\t\tmode: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: 'single'\n\t\t\t},\n\t\t\t// 日期行高\n\t\t\trowHeight: {\n\t\t\t\ttype: [String, Number],\n\t\t\t\tdefault: 58\n\t\t\t},\n\t\t\t// mode=multiple时，最多可选多少个日期\n\t\t\tmaxCount: {\n\t\t\t\ttype: [String, Number],\n\t\t\t\tdefault: Infinity\n\t\t\t},\n\t\t\t// mode=range时，第一个日期底部的提示文字\n\t\t\tstartText: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: '开始'\n\t\t\t},\n\t\t\t// mode=range时，最后一个日期底部的提示文字\n\t\t\tendText: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: '结束'\n\t\t\t},\n\t\t\t// 默认选中的日期，mode为multiple或range是必须为数组格式\n\t\t\tdefaultDate: {\n\t\t\t\ttype: [Array, String, Date],\n\t\t\t\tdefault: null\n\t\t\t},\n\t\t\t// 最小的可选日期\n\t\t\tminDate: {\n\t\t\t\ttype: [String, Number],\n\t\t\t\tdefault: 0\n\t\t\t},\n\t\t\t// 最大可选日期\n\t\t\tmaxDate: {\n\t\t\t\ttype: [String, Number],\n\t\t\t\tdefault: 0\n\t\t\t},\n\t\t\t// 如果没有设置maxDate，则往后推多少个月\n\t\t\tmaxMonth: {\n\t\t\t\ttype: [String, Number],\n\t\t\t\tdefault: 2\n\t\t\t},\n\t\t\t// 是否为只读状态，只读状态下禁止选择日期\n\t\t\treadonly: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: uni.$u.props.calendar.readonly\n\t\t\t},\n\t\t\t// 日期区间最多可选天数，默认无限制，mode = range时有效\n\t\t\tmaxRange: {\n\t\t\t\ttype: [Number, String],\n\t\t\t\tdefault: Infinity\n\t\t\t},\n\t\t\t// 范围选择超过最多可选天数时的提示文案，mode = range时有效\n\t\t\trangePrompt: {\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t},\n\t\t\t// 范围选择超过最多可选天数时，是否展示提示文案，mode = range时有效\n\t\t\tshowRangePrompt: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: true\n\t\t\t},\n\t\t\t// 是否允许日期范围的起止时间为同一天，mode = range时有效\n\t\t\tallowSameDay: {\n\t\t\t\ttype: Boolean,\n\t\t\t\tdefault: false\n\t\t\t}\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 每个日期的宽度\n\t\t\t\twidth: 0,\n\t\t\t\t// 当前选中的日期item\n\t\t\t\titem: {},\n\t\t\t\tselected: []\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tselectedChange: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(n) {\n\t\t\t\t\tthis.setDefaultDate()\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 多个条件的变化，会引起选中日期的变化，这里统一管理监听\n\t\t\tselectedChange() {\n\t\t\t\treturn [this.minDate, this.maxDate, this.defaultDate]\n\t\t\t},\n\t\t\tdayStyle(index1, index2, item) {\n\t\t\t\treturn (index1, index2, item) => {\n\t\t\t\t\tconst style = {}\n\t\t\t\t\tlet week = item.week\n\t\t\t\t\t// 不进行四舍五入的形式保留2位小数\n\t\t\t\t\tconst dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))\n\t\t\t\t\t// 得出每个日期的宽度\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\tstyle.width = uni.$u.addUnit(dayWidth)\n\t\t\t\t\t// #endif\n\t\t\t\t\tstyle.height = uni.$u.addUnit(this.rowHeight)\n\t\t\t\t\tif (index2 === 0) {\n\t\t\t\t\t\t// 获取当前为星期几，如果为0，则为星期天，减一为每月第一天时，需要向左偏移的item个数\n\t\t\t\t\t\tweek = (week === 0 ? 7 : week) - 1\n\t\t\t\t\t\tstyle.marginLeft = uni.$u.addUnit(week * dayWidth)\n\t\t\t\t\t}\n\t\t\t\t\tif (this.mode === 'range') {\n\t\t\t\t\t\t// 之所以需要这么写，是因为DCloud公司的iOS客户端的开发者能力有限导致的bug\n\t\t\t\t\t\tstyle.paddingLeft = 0\n\t\t\t\t\t\tstyle.paddingRight = 0\n\t\t\t\t\t\tstyle.paddingBottom = 0\n\t\t\t\t\t\tstyle.paddingTop = 0\n\t\t\t\t\t}\n\t\t\t\t\treturn style\n\t\t\t\t}\n\t\t\t},\n\t\t\tdaySelectStyle() {\n\t\t\t\treturn (index1, index2, item) => {\n\t\t\t\t\tlet date = dayjs(item.date).format(\"YYYY-MM-DD\"),\n\t\t\t\t\t\tstyle = {}\n\t\t\t\t\t// 判断date是否在selected数组中，因为月份可能会需要补0，所以使用dateSame判断，而不用数组的includes判断\n\t\t\t\t\tif (this.selected.some(item => this.dateSame(item, date))) {\n\t\t\t\t\t\tstyle.backgroundColor = this.color\n\t\t\t\t\t}\n\t\t\t\t\tif (this.mode === 'single') {\n\t\t\t\t\t\tif (date === this.selected[0]) {\n\t\t\t\t\t\t\t// 因为需要对nvue的兼容，只能这么写，无法缩写，也无法通过类名控制等等\n\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\n\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\n\t\t\t\t\t\t\tstyle.borderTopRightRadius = '3px'\n\t\t\t\t\t\t\tstyle.borderBottomRightRadius = '3px'\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (this.mode === 'range') {\n\t\t\t\t\t\tif (this.selected.length >= 2) {\n\t\t\t\t\t\t\tconst len = this.selected.length - 1\n\t\t\t\t\t\t\t// 第一个日期设置左上角和左下角的圆角\n\t\t\t\t\t\t\tif (this.dateSame(date, this.selected[0])) {\n\t\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\n\t\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 最后一个日期设置右上角和右下角的圆角\n\t\t\t\t\t\t\tif (this.dateSame(date, this.selected[len])) {\n\t\t\t\t\t\t\t\tstyle.borderTopRightRadius = '3px'\n\t\t\t\t\t\t\t\tstyle.borderBottomRightRadius = '3px'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 处于第一和最后一个之间的日期，背景色设置为浅色，通过将对应颜色进行等分，再取其尾部的颜色值\n\t\t\t\t\t\t\tif (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this\n\t\t\t\t\t\t\t\t\t.selected[len]))) {\n\t\t\t\t\t\t\t\tstyle.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90]\n\t\t\t\t\t\t\t\t// 增加一个透明度，让范围区间的背景色也能看到底部的mark水印字符\n\t\t\t\t\t\t\t\tstyle.opacity = 0.7\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else if (this.selected.length === 1) {\n\t\t\t\t\t\t\t// 之所以需要这么写，是因为DCloud公司的iOS客户端的开发者能力有限导致的bug\n\t\t\t\t\t\t\t// 进行还原操作，否则在nvue的iOS，uni-app有bug，会导致诡异的表现\n\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\n\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (this.selected.some(item => this.dateSame(item, date))) {\n\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\n\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\n\t\t\t\t\t\t\tstyle.borderTopRightRadius = '3px'\n\t\t\t\t\t\t\tstyle.borderBottomRightRadius = '3px'\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn style\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 某个日期是否被选中\n\t\t\ttextStyle() {\n\t\t\t\treturn (item) => {\n\t\t\t\t\tconst date = dayjs(item.date).format(\"YYYY-MM-DD\"),\n\t\t\t\t\t\tstyle = {}\n\t\t\t\t\t// 选中的日期，提示文字设置白色\n\t\t\t\t\tif (this.selected.some(item => this.dateSame(item, date))) {\n\t\t\t\t\t\tstyle.color = '#ffffff'\n\t\t\t\t\t}\n\t\t\t\t\tif (this.mode === 'range') {\n\t\t\t\t\t\tconst len = this.selected.length - 1\n\t\t\t\t\t\t// 如果是范围选择模式，第一个和最后一个之间的日期，文字颜色设置为高亮的主题色\n\t\t\t\t\t\tif (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this\n\t\t\t\t\t\t\t\t.selected[len]))) {\n\t\t\t\t\t\t\tstyle.color = this.color\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn style\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 获取底部的提示文字\n\t\t\tgetBottomInfo() {\n\t\t\t\treturn (index1, index2, item) => {\n\t\t\t\t\tconst date = dayjs(item.date).format(\"YYYY-MM-DD\")\n\t\t\t\t\tconst bottomInfo = item.bottomInfo\n\t\t\t\t\t// 当为日期范围模式时，且选择的日期个数大于0时\n\t\t\t\t\tif (this.mode === 'range' && this.selected.length > 0) {\n\t\t\t\t\t\tif (this.selected.length === 1) {\n\t\t\t\t\t\t\t// 选择了一个日期时，如果当前日期为数组中的第一个日期，则显示底部文字为“开始”\n\t\t\t\t\t\t\tif (this.dateSame(date, this.selected[0])) return this.startText\n\t\t\t\t\t\t\telse return bottomInfo\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst len = this.selected.length - 1\n\t\t\t\t\t\t\t// 如果数组中的日期大于2个时，第一个和最后一个显示为开始和结束日期\n\t\t\t\t\t\t\tif (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&\n\t\t\t\t\t\t\t\tlen === 1) {\n\t\t\t\t\t\t\t\t// 如果长度为2，且第一个等于第二个日期，则提示语放在同一个item中\n\t\t\t\t\t\t\t\treturn `${this.startText}/${this.endText}`\n\t\t\t\t\t\t\t} else if (this.dateSame(date, this.selected[0])) {\n\t\t\t\t\t\t\t\treturn this.startText\n\t\t\t\t\t\t\t} else if (this.dateSame(date, this.selected[len])) {\n\t\t\t\t\t\t\t\treturn this.endText\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\treturn bottomInfo\n\t\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\treturn bottomInfo\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 初始化默认选中\n\t\t\t\tthis.$emit('monthSelected', this.selected)\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t// 这里需要另一个延时，因为获取宽度后，会进行月份数据渲染，只有渲染完成之后，才有真正的高度\n\t\t\t\t\t// 因为nvue下，$nextTick并不是100%可靠的\n\t\t\t\t\tuni.$u.sleep(10).then(() => {\n\t\t\t\t\t\tthis.getWrapperWidth()\n\t\t\t\t\t\tthis.getMonthRect()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 判断两个日期是否相等\n\t\t\tdateSame(date1, date2) {\n\t\t\t\treturn dayjs(date1).isSame(dayjs(date2))\n\t\t\t},\n\t\t\t// 获取月份数据区域的宽度，因为nvue不支持百分比，所以无法通过css设置每个日期item的宽度\n\t\t\tgetWrapperWidth() {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tdom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {\n\t\t\t\t\tthis.width = res.size.width\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.$uGetRect('.u-calendar-month-wrapper').then(size => {\n\t\t\t\t\tthis.width = size.width\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\tgetMonthRect() {\n\t\t\t\t// 获取每个月份数据的尺寸，用于父组件在scroll-view滚动事件中，监听当前滚动到了第几个月份\n\t\t\t\tconst promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(\n\t\t\t\t\t`u-calendar-month-${index}`))\n\t\t\t\t// 一次性返回\n\t\t\t\tPromise.all(promiseAllArr).then(\n\t\t\t\t\tsizes => {\n\t\t\t\t\t\tlet height = 1\n\t\t\t\t\t\tconst topArr = []\n\t\t\t\t\t\tfor (let i = 0; i < this.months.length; i++) {\n\t\t\t\t\t\t\t// 添加到months数组中，供scroll-view滚动事件中，判断当前滚动到哪个月份\n\t\t\t\t\t\t\ttopArr[i] = height\n\t\t\t\t\t\t\theight += sizes[i].height\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// 由于微信下，无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值，所以使用事件形式对外发出\n\t\t\t\t\t\tthis.$emit('updateMonthTop', topArr)\n\t\t\t\t\t})\n\t\t\t},\n\t\t\t// 获取每个月份区域的尺寸\n\t\t\tgetMonthRectByPromise(el) {\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t// $uGetRect为uView自带的节点查询简化方法，详见文档介绍：https://www.uviewui.com/js/getRect.html\n\t\t\t\t// 组件内部一般用this.$uGetRect，对外的为uni.$u.getRect，二者功能一致，名称不同\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tthis.$uGetRect(`.${el}`).then(size => {\n\t\t\t\t\t\tresolve(size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// nvue下，使用dom模块查询元素高度\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tdom.getComponentRect(this.$refs[el][0], res => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 点击某一个日期\n\t\t\tclickHandler(index1, index2, item) {\n\t\t\t\tif (this.readonly) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.item = item\n\t\t\t\tconst date = dayjs(item.date).format(\"YYYY-MM-DD\")\n\t\t\t\tif (item.disabled) return\n\t\t\t\t// 对上一次选择的日期数组进行深度克隆\n\t\t\t\tlet selected = uni.$u.deepClone(this.selected)\n\t\t\t\tif (this.mode === 'single') {\n\t\t\t\t\t// 单选情况下，让数组中的元素为当前点击的日期\n\t\t\t\t\tselected = [date]\n\t\t\t\t} else if (this.mode === 'multiple') {\n\t\t\t\t\tif (selected.some(item => this.dateSame(item, date))) {\n\t\t\t\t\t\t// 如果点击的日期已在数组中，则进行移除操作，也就是达到反选的效果\n\t\t\t\t\t\tconst itemIndex = selected.findIndex(item => item === date)\n\t\t\t\t\t\tselected.splice(itemIndex, 1)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// 如果点击的日期不在数组中，且已有的长度小于总可选长度时，则添加到数组中去\n\t\t\t\t\t\tif (selected.length < this.maxCount) selected.push(date)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// 选择区间形式\n\t\t\t\t\tif (selected.length === 0 || selected.length >= 2) {\n\t\t\t\t\t\t// 如果原来就为0或者大于2的长度，则当前点击的日期，就是开始日期\n\t\t\t\t\t\tselected = [date]\n\t\t\t\t\t} else if (selected.length === 1) {\n\t\t\t\t\t\t// 如果已经选择了开始日期\n\t\t\t\t\t\tconst existsDate = selected[0]\n\t\t\t\t\t\t// 如果当前选择的日期小于上一次选择的日期，则当前的日期定为开始日期\n\t\t\t\t\t\tif (dayjs(date).isBefore(existsDate)) {\n\t\t\t\t\t\t\tselected = [date]\n\t\t\t\t\t\t} else if (dayjs(date).isAfter(existsDate)) {\n\t\t\t\t\t\t\t// 当前日期减去最大可选的日期天数，如果大于起始时间，则进行提示\n\t\t\t\t\t\t\tif(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {\n\t\t\t\t\t\t\t\tif(this.rangePrompt) {\n\t\t\t\t\t\t\t\t\tuni.$u.toast(this.rangePrompt)\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tuni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 如果当前日期大于已有日期，将当前的添加到数组尾部\n\t\t\t\t\t\t\tselected.push(date)\n\t\t\t\t\t\t\tconst startDate = selected[0]\n\t\t\t\t\t\t\tconst endDate = selected[1]\n\t\t\t\t\t\t\tconst arr = []\n\t\t\t\t\t\t\tlet i = 0\n\t\t\t\t\t\t\tdo {\n\t\t\t\t\t\t\t\t// 将开始和结束日期之间的日期添加到数组中\n\t\t\t\t\t\t\t\tarr.push(dayjs(startDate).add(i, 'day').format(\"YYYY-MM-DD\"))\n\t\t\t\t\t\t\t\ti++\n\t\t\t\t\t\t\t\t// 累加的日期小于结束日期时，继续下一次的循环\n\t\t\t\t\t\t\t} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))\n\t\t\t\t\t\t\t// 为了一次性修改数组，避免computed中多次触发，这里才用arr变量一次性赋值的方式，同时将最后一个日期添加近来\n\t\t\t\t\t\t\tarr.push(endDate)\n\t\t\t\t\t\t\tselected = arr\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// 选择区间时，只有一个日期的情况下，且不允许选择起止为同一天的话，不允许选择自己\n\t\t\t\t\t\t\tif (selected[0] === date && !this.allowSameDay) return\n\t\t\t\t\t\t\tselected.push(date)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.setSelected(selected)\n\t\t\t},\n\t\t\t// 设置默认日期\n\t\t\tsetDefaultDate() {\n\t\t\t\tif (!this.defaultDate) {\n\t\t\t\t\t// 如果没有设置默认日期，则将当天日期设置为默认选中的日期\n\t\t\t\t\tconst selected = [dayjs().format(\"YYYY-MM-DD\")]\n\t\t\t\t\treturn this.setSelected(selected, false)\n\t\t\t\t}\n\t\t\t\tlet defaultDate = []\n\t\t\t\tconst minDate = this.minDate || dayjs().format(\"YYYY-MM-DD\")\n\t\t\t\tconst maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format(\"YYYY-MM-DD\")\n\t\t\t\tif (this.mode === 'single') {\n\t\t\t\t\t// 单选模式，可以是字符串或数组，Date对象等\n\t\t\t\t\tif (!uni.$u.test.array(this.defaultDate)) {\n\t\t\t\t\t\tdefaultDate = [dayjs(this.defaultDate).format(\"YYYY-MM-DD\")]\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdefaultDate = [this.defaultDate[0]]\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// 如果为非数组，则不执行\n\t\t\t\t\tif (!uni.$u.test.array(this.defaultDate)) return\n\t\t\t\t\tdefaultDate = this.defaultDate\n\t\t\t\t}\n\t\t\t\t// 过滤用户传递的默认数组，取出只在可允许最大值与最小值之间的元素\n\t\t\t\tdefaultDate = defaultDate.filter(item => {\n\t\t\t\t\treturn dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(\n\t\t\t\t\t\tmaxDate).add(1, 'day'))\n\t\t\t\t})\n\t\t\t\tthis.setSelected(defaultDate, false)\n\t\t\t},\n\t\t\tsetSelected(selected, event = true) {\n\t\t\t\tthis.selected = selected\n\t\t\t\tevent && this.$emit('monthSelected', this.selected)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-calendar-month-wrapper {\n\t\tmargin-top: 4px;\n\t}\n\n\t.u-calendar-month {\n\n\t\t&__title {\n\t\t\tfont-size: 14px;\n\t\t\tline-height: 42px;\n\t\t\theight: 42px;\n\t\t\tcolor: $u-main-color;\n\t\t\ttext-align: center;\n\t\t\tfont-weight: bold;\n\t\t}\n\n\t\t&__days {\n\t\t\tposition: relative;\n\t\t\t@include flex;\n\t\t\tflex-wrap: wrap;\n\n\t\t\t&__month-mark-wrapper {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\tleft: 0;\n\t\t\t\tright: 0;\n\t\t\t\t@include flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\n\t\t\t\t&__text {\n\t\t\t\t\tfont-size: 155px;\n\t\t\t\t\tcolor: rgba(231, 232, 234, 0.83);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&__day {\n\t\t\t\t@include flex;\n\t\t\t\tpadding: 2px;\n\t\t\t\t/* #ifndef APP-NVUE */\n\t\t\t\t// vue下使用css进行宽度计算，因为某些安卓机会无法进行js获取父元素宽度进行计算得出，会有偏移\n\t\t\t\twidth: calc(100% / 7);\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\t/* #endif */\n\n\t\t\t\t&__select {\n\t\t\t\t\tflex: 1;\n\t\t\t\t\t@include flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\tposition: relative;\n\n\t\t\t\t\t&__dot {\n\t\t\t\t\t\twidth: 7px;\n\t\t\t\t\t\theight: 7px;\n\t\t\t\t\t\tborder-radius: 100px;\n\t\t\t\t\t\tbackground-color: $u-error;\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: 12px;\n\t\t\t\t\t\tright: 7px;\n\t\t\t\t\t}\n\n\t\t\t\t\t&__buttom-info {\n\t\t\t\t\t\tcolor: $u-content-color;\n\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tbottom: 5px;\n\t\t\t\t\t\tfont-size: 10px;\n\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\tright: 0;\n\n\t\t\t\t\t\t&--selected {\n\t\t\t\t\t\t\tcolor: #ffffff;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&--disabled {\n\t\t\t\t\t\t\tcolor: #cacbcd;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t&__info {\n\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\tfont-size: 16px;\n\n\t\t\t\t\t\t&--selected {\n\t\t\t\t\t\t\tcolor: #ffffff;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&--disabled {\n\t\t\t\t\t\t\tcolor: #cacbcd;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t&--selected {\n\t\t\t\t\t\tbackground-color: $u-primary;\n\t\t\t\t\t\t@include flex;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\tborder-radius: 3px;\n\t\t\t\t\t}\n\n\t\t\t\t\t&--range-selected {\n\t\t\t\t\t\topacity: 0.3;\n\t\t\t\t\t\tborder-radius: 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t&--range-start-selected {\n\t\t\t\t\t\tborder-top-right-radius: 0;\n\t\t\t\t\t\tborder-bottom-right-radius: 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t&--range-end-selected {\n\t\t\t\t\t\tborder-top-left-radius: 0;\n\t\t\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-calendar/props.js",
    "content": "export default {\n    props: {\n        // 日历顶部标题\n        title: {\n            type: String,\n            default: uni.$u.props.calendar.title\n        },\n        // 是否显示标题\n        showTitle: {\n            type: Boolean,\n            default: uni.$u.props.calendar.showTitle\n        },\n        // 是否显示副标题\n        showSubtitle: {\n            type: Boolean,\n            default: uni.$u.props.calendar.showSubtitle\n        },\n        // 日期类型选择，single-选择单个日期，multiple-可以选择多个日期，range-选择日期范围\n        mode: {\n            type: String,\n            default: uni.$u.props.calendar.mode\n        },\n        // mode=range时，第一个日期底部的提示文字\n        startText: {\n            type: String,\n            default: uni.$u.props.calendar.startText\n        },\n        // mode=range时，最后一个日期底部的提示文字\n        endText: {\n            type: String,\n            default: uni.$u.props.calendar.endText\n        },\n        // 自定义列表\n        customList: {\n            type: Array,\n            default: uni.$u.props.calendar.customList\n        },\n        // 主题色，对底部按钮和选中日期有效\n        color: {\n            type: String,\n            default: uni.$u.props.calendar.color\n        },\n        // 最小的可选日期\n        minDate: {\n            type: [String, Number],\n            default: uni.$u.props.calendar.minDate\n        },\n        // 最大可选日期\n        maxDate: {\n            type: [String, Number],\n            default: uni.$u.props.calendar.maxDate\n        },\n        // 默认选中的日期，mode为multiple或range是必须为数组格式\n        defaultDate: {\n            type: [Array, String, Date, null],\n            default: uni.$u.props.calendar.defaultDate\n        },\n        // mode=multiple时，最多可选多少个日期\n        maxCount: {\n            type: [String, Number],\n            default: uni.$u.props.calendar.maxCount\n        },\n        // 日期行高\n        rowHeight: {\n            type: [String, Number],\n            default: uni.$u.props.calendar.rowHeight\n        },\n        // 日期格式化函数\n        formatter: {\n            type: [Function, null],\n            default: uni.$u.props.calendar.formatter\n        },\n        // 是否显示农历\n        showLunar: {\n            type: Boolean,\n            default: uni.$u.props.calendar.showLunar\n        },\n        // 是否显示月份背景色\n        showMark: {\n            type: Boolean,\n            default: uni.$u.props.calendar.showMark\n        },\n        // 确定按钮的文字\n        confirmText: {\n            type: String,\n            default: uni.$u.props.calendar.confirmText\n        },\n        // 确认按钮处于禁用状态时的文字\n        confirmDisabledText: {\n            type: String,\n            default: uni.$u.props.calendar.confirmDisabledText\n        },\n        // 是否显示日历弹窗\n        show: {\n            type: Boolean,\n            default: uni.$u.props.calendar.show\n        },\n        // 是否允许点击遮罩关闭日历\n        closeOnClickOverlay: {\n            type: Boolean,\n            default: uni.$u.props.calendar.closeOnClickOverlay\n        },\n        // 是否为只读状态，只读状态下禁止选择日期\n        readonly: {\n            type: Boolean,\n            default: uni.$u.props.calendar.readonly\n        },\n        // \t是否展示确认按钮\n        showConfirm: {\n            type: Boolean,\n            default: uni.$u.props.calendar.showConfirm\n        },\n        // 日期区间最多可选天数，默认无限制，mode = range时有效\n        maxRange: {\n            type: [Number, String],\n            default: uni.$u.props.calendar.maxRange\n        },\n        // 范围选择超过最多可选天数时的提示文案，mode = range时有效\n        rangePrompt: {\n            type: String,\n            default: uni.$u.props.calendar.rangePrompt\n        },\n        // 范围选择超过最多可选天数时，是否展示提示文案，mode = range时有效\n        showRangePrompt: {\n            type: Boolean,\n            default: uni.$u.props.calendar.showRangePrompt\n        },\n        // 是否允许日期范围的起止时间为同一天，mode = range时有效\n        allowSameDay: {\n            type: Boolean,\n            default: uni.$u.props.calendar.allowSameDay\n        },\n\t\t// 圆角值\n\t\tround: {\n\t\t    type: [Boolean, String, Number],\n\t\t    default: uni.$u.props.calendar.round\n\t\t},\n\t\t// 最多展示月份数量\n\t\tmonthNum: {\n\t\t\ttype: [Number, String],\n\t\t\tdefault: 3\n\t\t}\t\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-calendar/u-calendar.vue",
    "content": "<template>\n\t<u-popup\n\t\t:show=\"show\"\n\t\tmode=\"bottom\"\n\t\tcloseable\n\t\t@close=\"close\"\n\t\t:round=\"round\"\n\t\t:closeOnClickOverlay=\"closeOnClickOverlay\"\n\t>\n\t\t<view class=\"u-calendar\">\n\t\t\t<uHeader\n\t\t\t\t:title=\"title\"\n\t\t\t\t:subtitle=\"subtitle\"\n\t\t\t\t:showSubtitle=\"showSubtitle\"\n\t\t\t\t:showTitle=\"showTitle\"\n\t\t\t></uHeader>\n\t\t\t<scroll-view\n\t\t\t\t:style=\"{\n                    height: $u.addUnit(listHeight)\n                }\"\n\t\t\t\tscroll-y\n\t\t\t\t@scroll=\"onScroll\"\n\t\t\t\t:scroll-top=\"scrollTop\"\n\t\t\t\t:scrollIntoView=\"scrollIntoView\"\n\t\t\t>\n\t\t\t\t<uMonth\n\t\t\t\t\t:color=\"color\"\n\t\t\t\t\t:rowHeight=\"rowHeight\"\n\t\t\t\t\t:showMark=\"showMark\"\n\t\t\t\t\t:months=\"months\"\n\t\t\t\t\t:mode=\"mode\"\n\t\t\t\t\t:maxCount=\"maxCount\"\n\t\t\t\t\t:startText=\"startText\"\n\t\t\t\t\t:endText=\"endText\"\n\t\t\t\t\t:defaultDate=\"defaultDate\"\n\t\t\t\t\t:minDate=\"innerMinDate\"\n\t\t\t\t\t:maxDate=\"innerMaxDate\"\n\t\t\t\t\t:maxMonth=\"monthNum\"\n\t\t\t\t\t:readonly=\"readonly\"\n\t\t\t\t\t:maxRange=\"maxRange\"\n\t\t\t\t\t:rangePrompt=\"rangePrompt\"\n\t\t\t\t\t:showRangePrompt=\"showRangePrompt\"\n\t\t\t\t\t:allowSameDay=\"allowSameDay\"\n\t\t\t\t\tref=\"month\"\n\t\t\t\t\t@monthSelected=\"monthSelected\"\n\t\t\t\t\t@updateMonthTop=\"updateMonthTop\"\n\t\t\t\t></uMonth>\n\t\t\t</scroll-view>\n\t\t\t<slot name=\"footer\" v-if=\"showConfirm\">\n\t\t\t\t<view class=\"u-calendar__confirm\">\n\t\t\t\t\t<u-button\n\t\t\t\t\t\tshape=\"circle\"\n\t\t\t\t\t\t:text=\"\n                            buttonDisabled ? confirmDisabledText : confirmText\n                        \"\n\t\t\t\t\t\t:color=\"color\"\n\t\t\t\t\t\t@click=\"confirm\"\n\t\t\t\t\t\t:disabled=\"buttonDisabled\"\n\t\t\t\t\t></u-button>\n\t\t\t\t</view>\n\t\t\t</slot>\n\t\t</view>\n\t</u-popup>\n</template>\n\n<script>\nimport uHeader from './header.vue'\nimport uMonth from './month.vue'\nimport props from './props.js'\nimport util from './util.js'\nimport dayjs from '../../libs/util/dayjs.js'\nimport Calendar from '../../libs/util/calendar.js'\n/**\n * Calendar 日历\n * @description  此组件用于单个选择日期，范围选择日期等，日历被包裹在底部弹起的容器中.\n * @tutorial https://www.uviewui.com/components/calendar.html\n *\n * @property {String}\t\t\t\ttitle\t\t\t\t标题内容 (默认 日期选择 )\n * @property {Boolean}\t\t\t\tshowTitle\t\t\t是否显示标题  (默认 true )\n * @property {Boolean}\t\t\t\tshowSubtitle\t\t是否显示副标题\t(默认 true )\n * @property {String}\t\t\t\tmode\t\t\t\t日期类型选择  single-选择单个日期，multiple-可以选择多个日期，range-选择日期范围 （ 默认 'single' )\n * @property {String}\t\t\t\tstartText\t\t\tmode=range时，第一个日期底部的提示文字  (默认 '开始' )\n * @property {String}\t\t\t\tendText\t\t\t\tmode=range时，最后一个日期底部的提示文字 (默认 '结束' )\n * @property {Array}\t\t\t\tcustomList\t\t\t自定义列表\n * @property {String}\t\t\t\tcolor\t\t\t\t主题色，对底部按钮和选中日期有效  (默认 ‘#3c9cff' )\n * @property {String | Number}\t\tminDate\t\t\t\t最小的可选日期\t (默认 0 )\n * @property {String | Number}\t\tmaxDate\t\t\t\t最大可选日期  (默认 0 )\n * @property {Array | String| Date}\tdefaultDate\t\t\t默认选中的日期，mode为multiple或range是必须为数组格式\n * @property {String | Number}\t\tmaxCount\t\t\tmode=multiple时，最多可选多少个日期  (默认 \tNumber.MAX_SAFE_INTEGER  )\n * @property {String | Number}\t\trowHeight\t\t\t日期行高 (默认 56 )\n * @property {Function}\t\t\t\tformatter\t\t\t日期格式化函数\n * @property {Boolean}\t\t\t\tshowLunar\t\t\t是否显示农历  (默认 false )\n * @property {Boolean}\t\t\t\tshowMark\t\t\t是否显示月份背景色 (默认 true )\n * @property {String}\t\t\t\tconfirmText\t\t\t确定按钮的文字 (默认 '确定' )\n * @property {String}\t\t\t\tconfirmDisabledText\t确认按钮处于禁用状态时的文字 (默认 '确定' )\n * @property {Boolean}\t\t\t\tshow\t\t\t\t是否显示日历弹窗 (默认 false )\n * @property {Boolean}\t\t\t\tcloseOnClickOverlay\t是否允许点击遮罩关闭日历 (默认 false )\n * @property {Boolean}\t\t\t\treadonly\t        是否为只读状态，只读状态下禁止选择日期 (默认 false )\n * @property {String | Number}\t\tmaxRange\t        日期区间最多可选天数，默认无限制，mode = range时有效\n * @property {String}\t\t\t\trangePrompt\t        范围选择超过最多可选天数时的提示文案，mode = range时有效\n * @property {Boolean}\t\t\t\tshowRangePrompt\t    范围选择超过最多可选天数时，是否展示提示文案，mode = range时有效 (默认 true )\n * @property {Boolean}\t\t\t\tallowSameDay\t    是否允许日期范围的起止时间为同一天，mode = range时有效 (默认 false )\n * @property {Number|String}\t    round\t\t\t\t圆角值，默认无圆角  (默认 0 )\n * @property {Number|String}\t    monthNum\t\t\t最多展示的月份数量  (默认 3 )\n *\n * @event {Function()} confirm \t\t点击确定按钮时触发\t\t选择日期相关的返回参数\n * @event {Function()} close \t\t日历关闭时触发\t\t\t可定义页面关闭时的回调事件\n * @example <u-calendar  :defaultDate=\"defaultDateMultiple\" :show=\"show\" mode=\"multiple\" @confirm=\"confirm\">\n\t</u-calendar>\n * */\nexport default {\n\tname: 'u-calendar',\n\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\tcomponents: {\n\t\tuHeader,\n\t\tuMonth\n\t},\n\tdata() {\n\t\treturn {\n\t\t\t// 需要显示的月份的数组\n\t\t\tmonths: [],\n\t\t\t// 在月份滚动区域中，当前视图中月份的index索引\n\t\t\tmonthIndex: 0,\n\t\t\t// 月份滚动区域的高度\n\t\t\tlistHeight: 0,\n\t\t\t// month组件中选择的日期数组\n\t\t\tselected: [],\n\t\t\tscrollIntoView: '',\n\t\t\tscrollTop:0,\n\t\t\t// 过滤处理方法\n\t\t\tinnerFormatter: (value) => value\n\t\t}\n\t},\n\twatch: {\n\t\tselectedChange: {\n\t\t\timmediate: true,\n\t\t\thandler(n) {\n\t\t\t\tthis.setMonth()\n\t\t\t}\n\t\t},\n\t\t// 打开弹窗时，设置月份数据\n\t\tshow: {\n\t\t\timmediate: true,\n\t\t\thandler(n) {\n\t\t\t\tthis.setMonth()\n\t\t\t}\n\t\t}\n\t},\n\tcomputed: {\n\t\t// 由于maxDate和minDate可以为字符串(2021-10-10)，或者数值(时间戳)，但是dayjs如果接受字符串形式的时间戳会有问题，这里进行处理\n\t\tinnerMaxDate() {\n\t\t\treturn uni.$u.test.number(this.maxDate)\n\t\t\t\t? Number(this.maxDate)\n\t\t\t\t: this.maxDate\n\t\t},\n\t\tinnerMinDate() {\n\t\t\treturn uni.$u.test.number(this.minDate)\n\t\t\t\t? Number(this.minDate)\n\t\t\t\t: this.minDate\n\t\t},\n\t\t// 多个条件的变化，会引起选中日期的变化，这里统一管理监听\n\t\tselectedChange() {\n\t\t\treturn [this.innerMinDate, this.innerMaxDate, this.defaultDate]\n\t\t},\n\t\tsubtitle() {\n\t\t\t// 初始化时，this.months为空数组，所以需要特别判断处理\n\t\t\tif (this.months.length) {\n\t\t\t\treturn `${this.months[this.monthIndex].year}年${\n\t\t\t\t\tthis.months[this.monthIndex].month\n\t\t\t\t}月`\n\t\t\t} else {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t},\n\t\tbuttonDisabled() {\n\t\t\t// 如果为range类型，且选择的日期个数不足1个时，让底部的按钮出于disabled状态\n\t\t\tif (this.mode === 'range') {\n\t\t\t\tif (this.selected.length <= 1) {\n\t\t\t\t\treturn true\n\t\t\t\t} else {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t},\n\tmounted() {\n\t\tthis.start = Date.now()\n\t\tthis.init()\n\t},\n\tmethods: {\n\t\t// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用\n\t\tsetFormatter(e) {\n\t\t\tthis.innerFormatter = e\n\t\t},\n\t\t// month组件内部选择日期后，通过事件通知给父组件\n\t\tmonthSelected(e) {\n\t\t\tthis.selected = e\n\t\t\tif (!this.showConfirm) {\n\t\t\t\t// 在不需要确认按钮的情况下，如果为单选，或者范围多选且已选长度大于2，则直接进行返还\n\t\t\t\tif (\n\t\t\t\t\tthis.mode === 'multiple' ||\n\t\t\t\t\tthis.mode === 'single' ||\n\t\t\t\t\t(this.mode === 'range' && this.selected.length >= 2)\n\t\t\t\t) {\n\t\t\t\t\tthis.$emit('confirm', this.selected)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tinit() {\n\t\t\t// 校验maxDate，不能小于minDate\n\t\t\tif (\n\t\t\t\tthis.innerMaxDate &&\n\t\t\t\tthis.innerMinDate &&\n\t\t\t\tnew Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()\n\t\t\t) {\n\t\t\t\treturn uni.$u.error('maxDate不能小于minDate')\n\t\t\t}\n\t\t\t// 滚动区域的高度\n\t\t\tthis.listHeight = this.rowHeight * 5 + 30\n\t\t\tthis.setMonth()\n\t\t},\n\t\tclose() {\n\t\t\tthis.$emit('close')\n\t\t},\n\t\t// 点击确定按钮\n\t\tconfirm() {\n\t\t\tif (!this.buttonDisabled) {\n\t\t\t\tthis.$emit('confirm', this.selected)\n\t\t\t}\n\t\t},\n\t\t// 获得两个日期之间的月份数\n\t\tgetMonths(minDate, maxDate) {\n\t\t\tconst minYear = dayjs(minDate).year()\n\t\t\tconst minMonth = dayjs(minDate).month() + 1\n\t\t\tconst maxYear = dayjs(maxDate).year()\n\t\t\tconst maxMonth = dayjs(maxDate).month() + 1\n\t\t\treturn (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1\n\t\t},\n\t\t// 设置月份数据\n\t\tsetMonth() {\n\t\t\t// 最小日期的毫秒数\n\t\t\tconst minDate = this.innerMinDate || dayjs().valueOf()\n\t\t\t// 如果没有指定最大日期，则往后推3个月\n\t\t\tconst maxDate =\n\t\t\t\tthis.innerMaxDate ||\n\t\t\t\tdayjs(minDate)\n\t\t\t\t\t.add(this.monthNum - 1, 'month')\n\t\t\t\t\t.valueOf()\n\t\t\t// 最大最小月份之间的共有多少个月份，\n\t\t\tconst months = uni.$u.range(\n\t\t\t\t1,\n\t\t\t\tthis.monthNum,\n\t\t\t\tthis.getMonths(minDate, maxDate)\n\t\t\t)\n\t\t\t// 先清空数组\n\t\t\tthis.months = []\n\t\t\tfor (let i = 0; i < months; i++) {\n\t\t\t\tthis.months.push({\n\t\t\t\t\tdate: new Array(\n\t\t\t\t\t\tdayjs(minDate).add(i, 'month').daysInMonth()\n\t\t\t\t\t)\n\t\t\t\t\t\t.fill(1)\n\t\t\t\t\t\t.map((item, index) => {\n\t\t\t\t\t\t\t// 日期，取值1-31\n\t\t\t\t\t\t\tlet day = index + 1\n\t\t\t\t\t\t\t// 星期，0-6，0为周日\n\t\t\t\t\t\t\tconst week = dayjs(minDate)\n\t\t\t\t\t\t\t\t.add(i, 'month')\n\t\t\t\t\t\t\t\t.date(day)\n\t\t\t\t\t\t\t\t.day()\n\t\t\t\t\t\t\tconst date = dayjs(minDate)\n\t\t\t\t\t\t\t\t.add(i, 'month')\n\t\t\t\t\t\t\t\t.date(day)\n\t\t\t\t\t\t\t\t.format('YYYY-MM-DD')\n\t\t\t\t\t\t\tlet bottomInfo = ''\n\t\t\t\t\t\t\tif (this.showLunar) {\n\t\t\t\t\t\t\t\t// 将日期转为农历格式\n\t\t\t\t\t\t\t\tconst lunar = Calendar.solar2lunar(\n\t\t\t\t\t\t\t\t\tdayjs(date).year(),\n\t\t\t\t\t\t\t\t\tdayjs(date).month() + 1,\n\t\t\t\t\t\t\t\t\tdayjs(date).date()\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\tbottomInfo = lunar.IDayCn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlet config = {\n\t\t\t\t\t\t\t\tday,\n\t\t\t\t\t\t\t\tweek,\n\t\t\t\t\t\t\t\t// 小于最小允许的日期，或者大于最大的日期，则设置为disabled状态\n\t\t\t\t\t\t\t\tdisabled:\n\t\t\t\t\t\t\t\t\tdayjs(date).isBefore(\n\t\t\t\t\t\t\t\t\t\tdayjs(minDate).format('YYYY-MM-DD')\n\t\t\t\t\t\t\t\t\t) ||\n\t\t\t\t\t\t\t\t\tdayjs(date).isAfter(\n\t\t\t\t\t\t\t\t\t\tdayjs(maxDate).format('YYYY-MM-DD')\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t// 返回一个日期对象，供外部的formatter获取当前日期的年月日等信息，进行加工处理\n\t\t\t\t\t\t\t\tdate: new Date(date),\n\t\t\t\t\t\t\t\tbottomInfo,\n\t\t\t\t\t\t\t\tdot: false,\n\t\t\t\t\t\t\t\tmonth:\n\t\t\t\t\t\t\t\t\tdayjs(minDate).add(i, 'month').month() + 1\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst formatter =\n\t\t\t\t\t\t\t\tthis.formatter || this.innerFormatter\n\t\t\t\t\t\t\treturn formatter(config)\n\t\t\t\t\t\t}),\n\t\t\t\t\t// 当前所属的月份\n\t\t\t\t\tmonth: dayjs(minDate).add(i, 'month').month() + 1,\n\t\t\t\t\t// 当前年份\n\t\t\t\t\tyear: dayjs(minDate).add(i, 'month').year()\n\t\t\t\t})\n\t\t\t}\n\n\t\t},\n\t\t// 滚动到默认设置的月份\n\t\tscrollIntoDefaultMonth(selected) {\n\t\t\t// 查询默认日期在可选列表的下标\n\t\t\tconst _index = this.months.findIndex(({\n\t\t\t\t  year,\n\t\t\t\t  month\n\t\t\t  }) => {\n\t\t\t\tmonth = uni.$u.padZero(month)\n\t\t\t\treturn `${year}-${month}` === selected\n\t\t\t})\n\t\t\tif (_index !== -1) {\n\t\t\t\t// #ifndef MP-WEIXIN\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tthis.scrollIntoView = `month-${_index}`\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t\t// #ifdef MP-WEIXIN\n\t\t\t\tthis.scrollTop = this.months[_index].top || 0;\n\t\t\t\t// #endif\n\t\t\t}\n\t\t},\n\t\t// scroll-view滚动监听\n\t\tonScroll(event) {\n\t\t\t// 不允许小于0的滚动值，如果scroll-view到顶了，继续下拉，会出现负数值\n\t\t\tconst scrollTop = Math.max(0, event.detail.scrollTop)\n\t\t\t// 将当前滚动条数值，除以滚动区域的高度，可以得出当前滚动到了哪一个月份的索引\n\t\t\tfor (let i = 0; i < this.months.length; i++) {\n\t\t\t\tif (scrollTop >= (this.months[i].top || this.listHeight)) {\n\t\t\t\t\tthis.monthIndex = i\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 更新月份的top值\n\t\tupdateMonthTop(topArr = []) {\n\t\t\t// 设置对应月份的top值，用于onScroll方法更新月份\n\t\t\ttopArr.map((item, index) => {\n\t\t\t\tthis.months[index].top = item\n\t\t\t})\n\n\t\t\t// 获取默认日期的下标\n\t\t\tif (!this.defaultDate) {\n\t\t\t\t// 如果没有设置默认日期，则将当天日期设置为默认选中的日期\n\t\t\t\tconst selected = dayjs().format(\"YYYY-MM\")\n\t\t\t\tthis.scrollIntoDefaultMonth(selected)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tlet selected = dayjs().format(\"YYYY-MM\");\n\t\t\t// 单选模式，可以是字符串或数组，Date对象等\n\t\t\tif (!uni.$u.test.array(this.defaultDate)) {\n\t\t\t\tselected = dayjs(this.defaultDate).format(\"YYYY-MM\")\n\t\t\t} else {\n\t\t\t\tselected = dayjs(this.defaultDate[0]).format(\"YYYY-MM\");\n\t\t\t}\n\t\t\tthis.scrollIntoDefaultMonth(selected)\n\t\t}\n\t}\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '../../libs/css/components.scss';\n\n.u-calendar {\n\t&__confirm {\n\t\tpadding: 7px 18px;\n\t}\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-calendar/util.js",
    "content": "export default {\n    methods: {\n        // 设置月份数据\n        setMonth() {\n            // 月初是周几\n            const day = dayjs(this.date).date(1).day()\n            const start = day == 0 ? 6 : day - 1\n\n            // 本月天数\n            const days = dayjs(this.date).endOf('month').format('D')\n\n            // 上个月天数\n            const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')\n\n            // 日期数据\n            const arr = []\n            // 清空表格\n            this.month = []\n\n            // 添加上月数据\n            arr.push(\n                ...new Array(start).fill(1).map((e, i) => {\n                    const day = prevDays - start + i + 1\n\n                    return {\n                        value: day,\n                        disabled: true,\n                        date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')\n                    }\n                })\n            )\n\n            // 添加本月数据\n            arr.push(\n                ...new Array(days - 0).fill(1).map((e, i) => {\n                    const day = i + 1\n\n                    return {\n                        value: day,\n                        date: dayjs(this.date).date(day).format('YYYY-MM-DD')\n                    }\n                })\n            )\n\n            // 添加下个月\n            arr.push(\n                ...new Array(42 - days - start).fill(1).map((e, i) => {\n                    const day = i + 1\n\n                    return {\n                        value: day,\n                        disabled: true,\n                        date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')\n                    }\n                })\n            )\n\n            // 分割数组\n            for (let n = 0; n < arr.length; n += 7) {\n                this.month.push(\n                    arr.slice(n, n + 7).map((e, i) => {\n                        e.index = i + n\n\n                        // 自定义信息\n                        const custom = this.customList.find((c) => c.date == e.date)\n\n                        // 农历\n                        if (this.lunar) {\n                            const {\n                                IDayCn,\n                                IMonthCn\n                            } = this.getLunar(e.date)\n                            e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn\n                        }\n\n                        return {\n                            ...e,\n                            ...custom\n                        }\n                    })\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-car-keyboard/props.js",
    "content": "export default {\n    props: {\n        // 是否打乱键盘按键的顺序\n        random: {\n            type: Boolean,\n            default: false\n        },\n        // 输入一个中文后，是否自动切换到英文\n        autoChange: {\n            type: Boolean,\n            default: false\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-car-keyboard/u-car-keyboard.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-keyboard\"\n\t\t@touchmove.stop.prevent=\"noop\"\n\t>\n\t\t<view\n\t\t\tv-for=\"(group, i) in abc ? engKeyBoardList : areaList\"\n\t\t\t:key=\"i\"\n\t\t\tclass=\"u-keyboard__button\"\n\t\t\t:index=\"i\"\n\t\t\t:class=\"[i + 1 === 4 && 'u-keyboard__button--center']\"\n\t\t>\n\t\t\t<view\n\t\t\t\tv-if=\"i === 3\"\n\t\t\t\tclass=\"u-keyboard__button__inner-wrapper\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-keyboard__button__inner-wrapper__left\"\n\t\t\t\t\thover-class=\"u-hover-class\"\n\t\t\t\t\t:hover-stay-time=\"200\"\n\t\t\t\t\t@tap=\"changeCarInputMode\"\n\t\t\t\t>\n\t\t\t\t\t<text\n\t\t\t\t\t\tclass=\"u-keyboard__button__inner-wrapper__left__lang\"\n\t\t\t\t\t\t:class=\"[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']\"\n\t\t\t\t\t>中</text>\n\t\t\t\t\t<text class=\"u-keyboard__button__inner-wrapper__left__line\">/</text>\n\t\t\t\t\t<text\n\t\t\t\t\t\tclass=\"u-keyboard__button__inner-wrapper__left__lang\"\n\t\t\t\t\t\t:class=\"[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']\"\n\t\t\t\t\t>英</text>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<view\n\t\t\t\tclass=\"u-keyboard__button__inner-wrapper\"\n\t\t\t\tv-for=\"(item, j) in group\"\n\t\t\t\t:key=\"j\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-keyboard__button__inner-wrapper__inner\"\n\t\t\t\t\t:hover-stay-time=\"200\"\n\t\t\t\t\t@tap=\"carInputClick(i, j)\"\n\t\t\t\t\thover-class=\"u-hover-class\"\n\t\t\t\t>\n\t\t\t\t\t<text class=\"u-keyboard__button__inner-wrapper__inner__text\">{{ item }}</text>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<view\n\t\t\t\tv-if=\"i === 3\"\n\t\t\t\t@touchstart=\"backspaceClick\"\n\t\t\t\t@touchend=\"clearTimer\"\n\t\t\t\tclass=\"u-keyboard__button__inner-wrapper\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-keyboard__button__inner-wrapper__right\"\n\t\t\t\t\thover-class=\"u-hover-class\"\n\t\t\t\t\t:hover-stay-time=\"200\"\n\t\t\t\t>\n\t\t\t\t\t<u-icon\n\t\t\t\t\t\tsize=\"28\"\n\t\t\t\t\t\tname=\"backspace\"\n\t\t\t\t\t\tcolor=\"#303133\"\n\t\t\t\t\t></u-icon>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * keyboard 键盘组件\n\t * @description 此为uView自定义的键盘面板，内含了数字键盘，车牌号键，身份证号键盘3种模式，都有可以打乱按键顺序的选项。\n\t * @tutorial https://uviewui.com/components/keyboard.html\n\t * @property {Boolean} random 是否打乱键盘的顺序\n\t * @event {Function} change 点击键盘触发\n\t * @event {Function} backspace 点击退格键触发\n\t * @example <u-keyboard ref=\"uKeyboard\" mode=\"car\" v-model=\"show\"></u-keyboard>\n\t */\n\texport default {\n\t\tname: \"u-keyboard\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 车牌输入时，abc=true为输入车牌号码，bac=false为输入省份中文简称\n\t\t\t\tabc: false\n\t\t\t};\n\t\t},\n\t\tcomputed: {\n\t\t\tareaList() {\n\t\t\t\tlet data = [\n\t\t\t\t\t'京',\n\t\t\t\t\t'沪',\n\t\t\t\t\t'粤',\n\t\t\t\t\t'津',\n\t\t\t\t\t'冀',\n\t\t\t\t\t'豫',\n\t\t\t\t\t'云',\n\t\t\t\t\t'辽',\n\t\t\t\t\t'黑',\n\t\t\t\t\t'湘',\n\t\t\t\t\t'皖',\n\t\t\t\t\t'鲁',\n\t\t\t\t\t'苏',\n\t\t\t\t\t'浙',\n\t\t\t\t\t'赣',\n\t\t\t\t\t'鄂',\n\t\t\t\t\t'桂',\n\t\t\t\t\t'甘',\n\t\t\t\t\t'晋',\n\t\t\t\t\t'陕',\n\t\t\t\t\t'蒙',\n\t\t\t\t\t'吉',\n\t\t\t\t\t'闽',\n\t\t\t\t\t'贵',\n\t\t\t\t\t'渝',\n\t\t\t\t\t'川',\n\t\t\t\t\t'青',\n\t\t\t\t\t'琼',\n\t\t\t\t\t'宁',\n\t\t\t\t\t'挂',\n\t\t\t\t\t'藏',\n\t\t\t\t\t'港',\n\t\t\t\t\t'澳',\n\t\t\t\t\t'新',\n\t\t\t\t\t'使',\n\t\t\t\t\t'学'\n\t\t\t\t];\n\t\t\t\tlet tmp = [];\n\t\t\t\t// 打乱顺序\n\t\t\t\tif (this.random) data = uni.$u.randomArray(data);\n\t\t\t\t// 切割成二维数组\n\t\t\t\ttmp[0] = data.slice(0, 10);\n\t\t\t\ttmp[1] = data.slice(10, 20);\n\t\t\t\ttmp[2] = data.slice(20, 30);\n\t\t\t\ttmp[3] = data.slice(30, 36);\n\t\t\t\treturn tmp;\n\t\t\t},\n\t\t\tengKeyBoardList() {\n\t\t\t\tlet data = [\n\t\t\t\t\t1,\n\t\t\t\t\t2,\n\t\t\t\t\t3,\n\t\t\t\t\t4,\n\t\t\t\t\t5,\n\t\t\t\t\t6,\n\t\t\t\t\t7,\n\t\t\t\t\t8,\n\t\t\t\t\t9,\n\t\t\t\t\t0,\n\t\t\t\t\t'Q',\n\t\t\t\t\t'W',\n\t\t\t\t\t'E',\n\t\t\t\t\t'R',\n\t\t\t\t\t'T',\n\t\t\t\t\t'Y',\n\t\t\t\t\t'U',\n\t\t\t\t\t'I',\n\t\t\t\t\t'O',\n\t\t\t\t\t'P',\n\t\t\t\t\t'A',\n\t\t\t\t\t'S',\n\t\t\t\t\t'D',\n\t\t\t\t\t'F',\n\t\t\t\t\t'G',\n\t\t\t\t\t'H',\n\t\t\t\t\t'J',\n\t\t\t\t\t'K',\n\t\t\t\t\t'L',\n\t\t\t\t\t'Z',\n\t\t\t\t\t'X',\n\t\t\t\t\t'C',\n\t\t\t\t\t'V',\n\t\t\t\t\t'B',\n\t\t\t\t\t'N',\n\t\t\t\t\t'M'\n\t\t\t\t];\n\t\t\t\tlet tmp = [];\n\t\t\t\tif (this.random) data = uni.$u.randomArray(data);\n\t\t\t\ttmp[0] = data.slice(0, 10);\n\t\t\t\ttmp[1] = data.slice(10, 20);\n\t\t\t\ttmp[2] = data.slice(20, 30);\n\t\t\t\ttmp[3] = data.slice(30, 36);\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击键盘按钮\n\t\t\tcarInputClick(i, j) {\n\t\t\t\tlet value = '';\n\t\t\t\t// 不同模式，获取不同数组的值\n\t\t\t\tif (this.abc) value = this.engKeyBoardList[i][j];\n\t\t\t\telse value = this.areaList[i][j];\n\t\t\t\t// 如果允许自动切换，则将中文状态切换为英文\n\t\t\t\tif (!this.abc && this.autoChange) uni.$u.sleep(200).then(() => this.abc = true)\n\t\t\t\tthis.$emit('change', value);\n\t\t\t},\n\t\t\t// 修改汽车牌键盘的输入模式，中文|英文\n\t\t\tchangeCarInputMode() {\n\t\t\t\tthis.abc = !this.abc;\n\t\t\t},\n\t\t\t// 点击退格键\n\t\t\tbackspaceClick() {\n\t\t\t\tthis.$emit('backspace');\n\t\t\t\tclearInterval(this.timer); //再次清空定时器，防止重复注册定时器\n\t\t\t\tthis.timer = null;\n\t\t\t\tthis.timer = setInterval(() => {\n\t\t\t\t\tthis.$emit('backspace');\n\t\t\t\t}, 250);\n\t\t\t},\n\t\t\tclearTimer() {\n\t\t\t\tclearInterval(this.timer);\n\t\t\t\tthis.timer = null;\n\t\t\t},\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-car-keyboard-background-color: rgb(224, 228, 230) !default;\n\t$u-car-keyboard-padding:6px 0 6px !default;\n\t$u-car-keyboard-button-inner-width:64rpx !default;\n\t$u-car-keyboard-button-inner-background-color:#FFFFFF !default;\n\t$u-car-keyboard-button-height:80rpx !default;\n\t$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;\n\t$u-car-keyboard-button-border-radius:4px !default;\n\t$u-car-keyboard-button-inner-margin:8rpx 5rpx !default;\n\t$u-car-keyboard-button-text-font-size:16px !default;\n\t$u-car-keyboard-button-text-color:$u-main-color !default;\n\t$u-car-keyboard-center-inner-margin: 0 4rpx !default;\n\t$u-car-keyboard-special-button-width:134rpx !default;\n\t$u-car-keyboard-lang-font-size:16px !default;\n\t$u-car-keyboard-lang-color:$u-main-color !default;\n\t$u-car-keyboard-active-color:$u-primary !default;\n\t$u-car-keyboard-line-font-size:15px !default;\n\t$u-car-keyboard-line-color:$u-main-color !default;\n\t$u-car-keyboard-line-margin:0 1px !default;\n\t$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;\n\n\t.u-keyboard {\n\t\t@include flex(column);\n\t\tjustify-content: space-around;\n\t\tbackground-color: $u-car-keyboard-background-color;\n\t\talign-items: stretch;\n\t\tpadding: $u-car-keyboard-padding;\n\n\t\t&__button {\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\tflex: 1;\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\t/* #endif */\n\n\t\t\t&__inner-wrapper {\n\t\t\t\tbox-shadow: $u-car-keyboard-button-inner-box-shadow;\n\t\t\t\tmargin: $u-car-keyboard-button-inner-margin;\n\t\t\t\tborder-radius: $u-car-keyboard-button-border-radius;\n\n\t\t\t\t&__inner {\n\t\t\t\t\t@include flex;\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\twidth: $u-car-keyboard-button-inner-width;\n\t\t\t\t\tbackground-color: $u-car-keyboard-button-inner-background-color;\n\t\t\t\t\theight: $u-car-keyboard-button-height;\n\t\t\t\t\tborder-radius: $u-car-keyboard-button-border-radius;\n\n\t\t\t\t\t&__text {\n\t\t\t\t\t\tfont-size: $u-car-keyboard-button-text-font-size;\n\t\t\t\t\t\tcolor: $u-car-keyboard-button-text-color;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&__left,\n\t\t\t\t&__right {\n\t\t\t\t\tborder-radius: $u-car-keyboard-button-border-radius;\n\t\t\t\t\twidth: $u-car-keyboard-special-button-width;\n\t\t\t\t\theight: $u-car-keyboard-button-height;\n\t\t\t\t\tbackground-color: $u-car-keyboard-u-hover-class-background-color;\n\t\t\t\t\t@include flex;\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tbox-shadow: $u-car-keyboard-button-inner-box-shadow;\n\t\t\t\t}\n\n\t\t\t\t&__left {\n\t\t\t\t\t&__line {\n\t\t\t\t\t\tfont-size: $u-car-keyboard-line-font-size;\n\t\t\t\t\t\tcolor: $u-car-keyboard-line-color;\n\t\t\t\t\t\tmargin: $u-car-keyboard-line-margin;\n\t\t\t\t\t}\n\n\t\t\t\t\t&__lang {\n\t\t\t\t\t\tfont-size: $u-car-keyboard-lang-font-size;\n\t\t\t\t\t\tcolor: $u-car-keyboard-lang-color;\n\n\t\t\t\t\t\t&--active {\n\t\t\t\t\t\t\tcolor: $u-car-keyboard-active-color;\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.u-hover-class {\n\t\tbackground-color: $u-car-keyboard-u-hover-class-background-color;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-cell/props.js",
    "content": "export default {\n    props: {\n        // 标题\n        title: {\n            type: [String, Number],\n            default: uni.$u.props.cell.title\n        },\n        // 标题下方的描述信息\n        label: {\n            type: [String, Number],\n            default: uni.$u.props.cell.label\n        },\n        // 右侧的内容\n        value: {\n            type: [String, Number],\n            default: uni.$u.props.cell.value\n        },\n        // 左侧图标名称，或者图片链接(本地文件建议使用绝对地址)\n        icon: {\n            type: String,\n            default: uni.$u.props.cell.icon\n        },\n        // 是否禁用cell\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.cell.disabled\n        },\n        // 是否显示下边框\n        border: {\n            type: Boolean,\n            default: uni.$u.props.cell.border\n        },\n        // 内容是否垂直居中(主要是针对右侧的value部分)\n        center: {\n            type: Boolean,\n            default: uni.$u.props.cell.center\n        },\n        // 点击后跳转的URL地址\n        url: {\n            type: String,\n            default: uni.$u.props.cell.url\n        },\n        // 链接跳转的方式，内部使用的是uView封装的route方法，可能会进行拦截操作\n        linkType: {\n            type: String,\n            default: uni.$u.props.cell.linkType\n        },\n        // 是否开启点击反馈(表现为点击时加上灰色背景)\n        clickable: {\n            type: Boolean,\n            default: uni.$u.props.cell.clickable\n        },\n        // 是否展示右侧箭头并开启点击反馈\n        isLink: {\n            type: Boolean,\n            default: uni.$u.props.cell.isLink\n        },\n        // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)\n        required: {\n            type: Boolean,\n            default: uni.$u.props.cell.required\n        },\n        // 右侧的图标箭头\n        rightIcon: {\n            type: String,\n            default: uni.$u.props.cell.rightIcon\n        },\n        // 右侧箭头的方向，可选值为：left，up，down\n        arrowDirection: {\n            type: String,\n            default: uni.$u.props.cell.arrowDirection\n        },\n        // 左侧图标样式\n        iconStyle: {\n            type: [Object, String],\n            default: () => {\n\t\t\t\treturn uni.$u.props.cell.iconStyle\n\t\t\t}\n        },\n        // 右侧箭头图标的样式\n        rightIconStyle: {\n            type: [Object, String],\n            default: () => {\n\t\t\t\treturn uni.$u.props.cell.rightIconStyle\n\t\t\t}\n        },\n        // 标题的样式\n        titleStyle: {\n            type: [Object, String],\n\t\t\tdefault: () => {\n\t\t\t\treturn uni.$u.props.cell.titleStyle\n\t\t\t}\n        },\n        // 单位元的大小，可选值为large\n        size: {\n            type: String,\n            default: uni.$u.props.cell.size\n        },\n        // 点击cell是否阻止事件传播\n        stop: {\n            type: Boolean,\n            default: uni.$u.props.cell.stop\n        },\n        // 标识符，cell被点击时返回\n        name: {\n            type: [Number, String],\n            default: uni.$u.props.cell.name\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-cell/u-cell.vue",
    "content": "<template>\n\t<view class=\"u-cell\" :class=\"[customClass]\" :style=\"[$u.addStyle(customStyle)]\"\n\t\t:hover-class=\"(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''\" :hover-stay-time=\"250\"\n\t\t@tap=\"clickHandler\">\n\t\t<view class=\"u-cell__body\" :class=\"[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']\">\n\t\t\t<view class=\"u-cell__body__content\">\n\t\t\t\t<view class=\"u-cell__left-icon-wrap\" v-if=\"$slots.icon || icon\">\n\t\t\t\t\t<slot name=\"icon\" v-if=\"$slots.icon\">\n\t\t\t\t\t</slot>\n\t\t\t\t\t<u-icon v-else :name=\"icon\" :custom-style=\"iconStyle\" :size=\"size === 'large' ? 22 : 18\"></u-icon>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"u-cell__title\">\n\t\t\t\t\t<slot name=\"title\">\n\t\t\t\t\t\t<text v-if=\"title\" class=\"u-cell__title-text\" :style=\"[titleTextStyle]\"\n\t\t\t\t\t\t\t:class=\"[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']\">{{ title }}</text>\n\t\t\t\t\t</slot>\n\t\t\t\t\t<slot name=\"label\">\n\t\t\t\t\t\t<text class=\"u-cell__label\" v-if=\"label\"\n\t\t\t\t\t\t\t:class=\"[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']\">{{ label }}</text>\n\t\t\t\t\t</slot>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<slot name=\"value\">\n\t\t\t\t<text class=\"u-cell__value\"\n\t\t\t\t\t:class=\"[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']\"\n\t\t\t\t\tv-if=\"!$u.test.empty(value)\">{{ value }}</text>\n\t\t\t</slot>\n\t\t\t<view class=\"u-cell__right-icon-wrap\" v-if=\"$slots['right-icon'] || isLink\"\n\t\t\t\t:class=\"[`u-cell__right-icon-wrap--${arrowDirection}`]\">\n\t\t\t\t<slot name=\"right-icon\" v-if=\"$slots['right-icon']\">\n\t\t\t\t</slot>\n\t\t\t\t<u-icon v-else :name=\"rightIcon\" :custom-style=\"rightIconStyle\" :color=\"disabled ? '#c8c9cc' : 'info'\"\n\t\t\t\t\t:size=\"size === 'large' ? 18 : 16\"></u-icon>\n\t\t\t</view>\n\t\t</view>\n\t\t<u-line v-if=\"border\"></u-line>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * cell  单元格\n\t * @description cell单元格一般用于一组列表的情况，比如个人中心页，设置页等。\n\t * @tutorial https://uviewui.com/components/cell.html\n\t * @property {String | Number}\ttitle\t\t\t标题\n\t * @property {String | Number}\tlabel\t\t\t标题下方的描述信息\n\t * @property {String | Number}\tvalue\t\t\t右侧的内容\n\t * @property {String}\t\t\ticon\t\t\t左侧图标名称，或者图片链接(本地文件建议使用绝对地址)\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用cell\t\n\t * @property {Boolean}\t\t\tborder\t\t\t是否显示下边框 (默认 true )\n\t * @property {Boolean}\t\t\tcenter\t\t\t内容是否垂直居中(主要是针对右侧的value部分) (默认 false )\n\t * @property {String}\t\t\turl\t\t\t\t点击后跳转的URL地址\n\t * @property {String}\t\t\tlinkType\t\t链接跳转的方式，内部使用的是uView封装的route方法，可能会进行拦截操作 (默认 'navigateTo' )\n\t * @property {Boolean}\t\t\tclickable\t\t是否开启点击反馈(表现为点击时加上灰色背景) （默认 false ） \n\t * @property {Boolean}\t\t\tisLink\t\t\t是否展示右侧箭头并开启点击反馈 （默认 false ）\n\t * @property {Boolean}\t\t\trequired\t\t是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) （默认 false ）\n\t * @property {String}\t\t\trightIcon\t\t右侧的图标箭头 （默认 'arrow-right'）\n\t * @property {String}\t\t\tarrowDirection\t右侧箭头的方向，可选值为：left，up，down\n\t * @property {Object | String}\t\t\trightIconStyle\t右侧箭头图标的样式\n\t * @property {Object | String}\t\t\ttitleStyle\t\t标题的样式\n\t * @property {Object | String}\t\t\ticonStyle\t\t左侧图标样式\n\t * @property {String}\t\t\tsize\t\t\t单位元的大小，可选值为 large，normal，mini \n\t * @property {Boolean}\t\t\tstop\t\t\t点击cell是否阻止事件传播 (默认 true )\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\n\t * \n\t * @event {Function}\t\t\tclick\t\t\t点击cell列表时触发\n\t * @example 该组件需要搭配cell-group组件使用，见官方文档示例\n\t */\n\texport default {\n\t\tname: 'u-cell',\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tcomputed: {\n\t\t\ttitleTextStyle() {\n\t\t\t\treturn uni.$u.addStyle(this.titleStyle)\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击cell\n\t\t\tclickHandler(e) {\n\t\t\t\tif (this.disabled) return\n\t\t\t\tthis.$emit('click', {\n\t\t\t\t\tname: this.name\n\t\t\t\t})\n\t\t\t\t// 如果配置了url(此props参数通过mixin引入)参数，跳转页面\n\t\t\t\tthis.openPage()\n\t\t\t\t// 是否阻止事件传播\n\t\t\t\tthis.stop && this.preventEvent(e)\n\t\t\t},\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t$u-cell-padding: 10px 15px !default;\n\t$u-cell-font-size: 15px !default;\n\t$u-cell-line-height: 24px !default;\n\t$u-cell-color: $u-main-color !default;\n\t$u-cell-icon-size: 16px !default;\n\t$u-cell-title-font-size: 15px !default;\n\t$u-cell-title-line-height: 22px !default;\n\t$u-cell-title-color: $u-main-color !default;\n\t$u-cell-label-font-size: 12px !default;\n\t$u-cell-label-color: $u-tips-color !default;\n\t$u-cell-label-line-height: 18px !default;\n\t$u-cell-value-font-size: 14px !default;\n\t$u-cell-value-color: $u-content-color !default;\n\t$u-cell-clickable-color: $u-bg-color !default;\n\t$u-cell-disabled-color: #c8c9cc !default;\n\t$u-cell-padding-top-large: 13px !default;\n\t$u-cell-padding-bottom-large: 13px !default;\n\t$u-cell-value-font-size-large: 15px !default;\n\t$u-cell-label-font-size-large: 14px !default;\n\t$u-cell-title-font-size-large: 16px !default;\n\t$u-cell-left-icon-wrap-margin-right: 4px !default;\n\t$u-cell-right-icon-wrap-margin-left: 4px !default;\n\t$u-cell-title-flex:1 !default;\n\t$u-cell-label-margin-top:5px !default;\n\n\n\t.u-cell {\n\t\t&__body {\n\t\t\t@include flex();\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tbox-sizing: border-box;\n\t\t\t/* #endif */\n\t\t\tpadding: $u-cell-padding;\n\t\t\tfont-size: $u-cell-font-size;\n\t\t\tcolor: $u-cell-color;\n\t\t\t// line-height: $u-cell-line-height;\n\t\t\talign-items: center;\n\n\t\t\t&__content {\n\t\t\t\t@include flex(row);\n\t\t\t\talign-items: center;\n\t\t\t\tflex: 1;\n\t\t\t}\n\n\t\t\t&--large {\n\t\t\t\tpadding-top: $u-cell-padding-top-large;\n\t\t\t\tpadding-bottom: $u-cell-padding-bottom-large;\n\t\t\t}\n\t\t}\n\n\t\t&__left-icon-wrap,\n\t\t&__right-icon-wrap {\n\t\t\t@include flex();\n\t\t\talign-items: center;\n\t\t\t// height: $u-cell-line-height;\n\t\t\tfont-size: $u-cell-icon-size;\n\t\t}\n\n\t\t&__left-icon-wrap {\n\t\t\tmargin-right: $u-cell-left-icon-wrap-margin-right;\n\t\t}\n\n\t\t&__right-icon-wrap {\n\t\t\tmargin-left: $u-cell-right-icon-wrap-margin-left;\n\t\t\ttransition: transform 0.3s;\n\n\t\t\t&--up {\n\t\t\t\ttransform: rotate(-90deg);\n\t\t\t}\n\n\t\t\t&--down {\n\t\t\t\ttransform: rotate(90deg);\n\t\t\t}\n\t\t}\n\n\t\t&__title {\n\t\t\tflex: $u-cell-title-flex;\n\n\t\t\t&-text {\n\t\t\t\tfont-size: $u-cell-title-font-size;\n\t\t\t\tline-height: $u-cell-title-line-height;\n\t\t\t\tcolor: $u-cell-title-color;\n\n\t\t\t\t&--large {\n\t\t\t\t\tfont-size: $u-cell-title-font-size-large;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\t&__label {\n\t\t\tmargin-top: $u-cell-label-margin-top;\n\t\t\tfont-size: $u-cell-label-font-size;\n\t\t\tcolor: $u-cell-label-color;\n\t\t\tline-height: $u-cell-label-line-height;\n\n\t\t\t&--large {\n\t\t\t\tfont-size: $u-cell-label-font-size-large;\n\t\t\t}\n\t\t}\n\n\t\t&__value {\n\t\t\ttext-align: right;\n\t\t\tfont-size: $u-cell-value-font-size;\n\t\t\tline-height: $u-cell-line-height;\n\t\t\tcolor: $u-cell-value-color;\n\n\t\t\t&--large {\n\t\t\t\tfont-size: $u-cell-value-font-size-large;\n\t\t\t}\n\t\t}\n\n\t\t&--clickable {\n\t\t\tbackground-color: $u-cell-clickable-color;\n\t\t}\n\n\t\t&--disabled {\n\t\t\tcolor: $u-cell-disabled-color;\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tcursor: not-allowed;\n\t\t\t/* #endif */\n\t\t}\n\n\t\t&--center {\n\t\t\talign-items: center;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-cell-group/props.js",
    "content": "export default {\n    props: {\n        // 分组标题\n        title: {\n            type: String,\n            default: uni.$u.props.cellGroup.title\n        },\n        // 是否显示外边框\n        border: {\n            type: Boolean,\n            default: uni.$u.props.cellGroup.border\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-cell-group/u-cell-group.vue",
    "content": "<template>\n    <view :style=\"[$u.addStyle(customStyle)]\" :class=\"[customClass]\" class=\"u-cell-group\">\n        <view v-if=\"title\" class=\"u-cell-group__title\">\n            <slot name=\"title\">\n\t\t\t\t<text class=\"u-cell-group__title__text\">{{ title }}</text>\n\t\t\t</slot>\n        </view>\n        <view class=\"u-cell-group__wrapper\">\n\t\t\t<u-line v-if=\"border\"></u-line>\n            <slot />\n        </view>\n    </view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * cellGroup  单元格\n\t * @description cell单元格一般用于一组列表的情况，比如个人中心页，设置页等。\n\t * @tutorial https://uviewui.com/components/cell.html\n\t * \n\t * @property {String}\ttitle\t\t分组标题\n\t * @property {Boolean}\tborder\t\t是否显示外边框 (默认 true )\n\t * @property {Object}\tcustomStyle\t定义需要用到的外部样式\n\t * \n\t * @event {Function} click \t点击cell列表时触发\n\t * @example <u-cell-group title=\"设置喜好\">\n\t */\n\texport default {\n\t\tname: 'u-cell-group',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t\n\t$u-cell-group-title-padding: 16px 16px 8px !default;\n\t$u-cell-group-title-font-size: 15px !default;\n\t$u-cell-group-title-line-height: 16px !default;\n\t$u-cell-group-title-color: $u-main-color !default;\n\n    .u-cell-group {\n\t\tflex: 1;\n\t\t\n        &__title {\n            padding: $u-cell-group-title-padding;\n\n            &__text {\n                font-size: $u-cell-group-title-font-size;\n                line-height: $u-cell-group-title-line-height;\n                color: $u-cell-group-title-color;\n            }\n        }\n\t\t\n\t\t&__wrapper {\n\t\t\tposition: relative;\n\t\t}\n    }\n</style>\n\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-checkbox/props.js",
    "content": "export default {\n    props: {\n        // checkbox的名称\n        name: {\n            type: [String, Number, Boolean],\n            default: uni.$u.props.checkbox.name\n        },\n        // 形状，square为方形，circle为圆型\n        shape: {\n            type: String,\n            default: uni.$u.props.checkbox.shape\n        },\n        // 整体的大小\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.checkbox.size\n        },\n        // 是否默认选中\n        checked: {\n            type: Boolean,\n            default: uni.$u.props.checkbox.checked\n        },\n        // 是否禁用\n        disabled: {\n            type: [String, Boolean],\n            default: uni.$u.props.checkbox.disabled\n        },\n        // 选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\n        activeColor: {\n            type: String,\n            default: uni.$u.props.checkbox.activeColor\n        },\n        // 未选中的颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.checkbox.inactiveColor\n        },\n        // 图标的大小，单位px\n        iconSize: {\n            type: [String, Number],\n            default: uni.$u.props.checkbox.iconSize\n        },\n        // 图标颜色\n        iconColor: {\n            type: String,\n            default: uni.$u.props.checkbox.iconColor\n        },\n        // label提示文字，因为nvue下，直接slot进来的文字，由于特殊的结构，无法修改样式\n        label: {\n            type: [String, Number],\n            default: uni.$u.props.checkbox.label\n        },\n        // label的字体大小，px单位\n        labelSize: {\n            type: [String, Number],\n            default: uni.$u.props.checkbox.labelSize\n        },\n        // label的颜色\n        labelColor: {\n            type: String,\n            default: uni.$u.props.checkbox.labelColor\n        },\n        // 是否禁止点击提示语选中复选框\n        labelDisabled: {\n            type: [String, Boolean],\n            default: uni.$u.props.checkbox.labelDisabled\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-checkbox/u-checkbox.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-checkbox\"\n\t    :style=\"[checkboxStyle]\"\n\t    @tap.stop=\"wrapperClickHandler\"\n\t    :class=\"[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']\"\n\t>\n\t\t<view\n\t\t    class=\"u-checkbox__icon-wrap\"\n\t\t    @tap.stop=\"iconClickHandler\"\n\t\t    :class=\"iconClasses\"\n\t\t    :style=\"[iconWrapStyle]\"\n\t\t>\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<u-icon\n\t\t\t\t    class=\"u-checkbox__icon-wrap__icon\"\n\t\t\t\t    name=\"checkbox-mark\"\n\t\t\t\t    :size=\"elIconSize\"\n\t\t\t\t    :color=\"elIconColor\"\n\t\t\t\t/>\n\t\t\t</slot>\n\t\t</view>\n\t\t<text\n\t\t    @tap.stop=\"labelClickHandler\"\n\t\t    :style=\"{\n\t\t\t\tcolor: elDisabled ? elInactiveColor : elLabelColor,\n\t\t\t\tfontSize: elLabelSize,\n\t\t\t\tlineHeight: elLabelSize\n\t\t\t}\"\n\t\t>{{label}}</text>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * checkbox  复选框\n\t * @description 复选框组件一般用于需要多个选择的场景，该组件功能完整，使用方便\n\t * @tutorial https://uviewui.com/components/checkbox.html\n\t * @property {String | Number | Boolean}\tname\t\t\tcheckbox组件的标示符\n\t * @property {String}\t\t\t\t\t\tshape\t\t\t形状，square为方形，circle为圆型\n\t * @property {String | Number}\t\t\t\tsize\t\t\t整体的大小\n\t * @property {Boolean}\t\t\t\t\t\tchecked\t\t\t是否默认选中\n\t * @property {String | Boolean}\t\t\t\tdisabled\t\t是否禁用\n\t * @property {String}\t\t\t\t\t\tactiveColor\t\t选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\n\t * @property {String}\t\t\t\t\t\tinactiveColor\t未选中的颜色\n\t * @property {String | Number}\t\t\t\ticonSize\t\t图标的大小，单位px\n\t * @property {String}\t\t\t\t\t\ticonColor\t\t图标颜色\n\t * @property {String | Number}\t\t\t\tlabel\t\t\tlabel提示文字，因为nvue下，直接slot进来的文字，由于特殊的结构，无法修改样式\n\t * @property {String}\t\t\t\t\t\tlabelColor \t\tlabel的颜色\n\t * @property {String | Number}\t\t\t\tlabelSize\t\tlabel的字体大小，px单位\n\t * @property {String | Boolean}\t\t\t\tlabelDisabled\t是否禁止点击提示语选中复选框\n\t * @property {Object}\t\t\t\t\t\tcustomStyle\t\t定义需要用到的外部样式\n\t * \n\t * @event {Function}\tchange\t任一个checkbox状态发生变化时触发，回调为一个对象\n\t * @example <u-checkbox v-model=\"checked\" :disabled=\"false\">天涯</u-checkbox>\n\t */\n\texport default {\n\t\tname: \"u-checkbox\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisChecked: false,\n\t\t\t\t// 父组件的默认值，因为头条小程序不支持在computed中使用this.parent.shape的形式\n\t\t\t\t// 故只能使用如此方法\n\t\t\t\tparentData: {\n\t\t\t\t\ticonSize: 12,\n\t\t\t\t\tlabelDisabled: null,\n\t\t\t\t\tdisabled: null,\n\t\t\t\t\tshape: 'square',\n\t\t\t\t\tactiveColor: null,\n\t\t\t\t\tinactiveColor: null,\n\t\t\t\t\tsize: 18,\n\t\t\t\t\tvalue: null,\n\t\t\t\t\ticonColor: null,\n\t\t\t\t\tplacement: 'row',\n\t\t\t\t\tborderBottom: false,\n\t\t\t\t\ticonPlacement: 'left'\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 是否禁用，如果父组件u-raios-group禁用的话，将会忽略子组件的配置\n\t\t\telDisabled() {\n\t\t\t\treturn this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;\n\t\t\t},\n\t\t\t// 是否禁用label点击\n\t\t\telLabelDisabled() {\n\t\t\t\treturn this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :\n\t\t\t\t\tfalse;\n\t\t\t},\n\t\t\t// 组件尺寸，对应size的值，默认值为21px\n\t\t\telSize() {\n\t\t\t\treturn this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);\n\t\t\t},\n\t\t\t// 组件的勾选图标的尺寸，默认12px\n\t\t\telIconSize() {\n\t\t\t\treturn this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);\n\t\t\t},\n\t\t\t// 组件选中激活时的颜色\n\t\t\telActiveColor() {\n\t\t\t\treturn this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');\n\t\t\t},\n\t\t\t// 组件选未中激活时的颜色\n\t\t\telInactiveColor() {\n\t\t\t\treturn this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :\n\t\t\t\t\t'#c8c9cc');\n\t\t\t},\n\t\t\t// label的颜色\n\t\t\telLabelColor() {\n\t\t\t\treturn this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')\n\t\t\t},\n\t\t\t// 组件的形状\n\t\t\telShape() {\n\t\t\t\treturn this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');\n\t\t\t},\n\t\t\t// label大小\n\t\t\telLabelSize() {\n\t\t\t\treturn uni.$u.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :\n\t\t\t\t\t'15'))\n\t\t\t},\n\t\t\telIconColor() {\n\t\t\t\tconst iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :\n\t\t\t\t\t'#ffffff');\n\t\t\t\t// 图标的颜色\n\t\t\t\tif (this.elDisabled) {\n\t\t\t\t\t// disabled状态下，已勾选的checkbox图标改为elInactiveColor\n\t\t\t\t\treturn this.isChecked ? this.elInactiveColor : 'transparent'\n\t\t\t\t} else {\n\t\t\t\t\treturn this.isChecked ? iconColor : 'transparent'\n\t\t\t\t}\n\t\t\t},\n\t\t\ticonClasses() {\n\t\t\t\tlet classes = []\n\t\t\t\t// 组件的形状\n\t\t\t\tclasses.push('u-checkbox__icon-wrap--' + this.elShape)\n\t\t\t\tif (this.elDisabled) {\n\t\t\t\t\tclasses.push('u-checkbox__icon-wrap--disabled')\n\t\t\t\t}\n\t\t\t\tif (this.isChecked && this.elDisabled) {\n\t\t\t\t\tclasses.push('u-checkbox__icon-wrap--disabled--checked')\n\t\t\t\t}\n\t\t\t\t// 支付宝，头条小程序无法动态绑定一个数组类名，否则解析出来的结果会带有\",\"，而导致失效\n\t\t\t\t// #ifdef MP-ALIPAY || MP-TOUTIAO\n\t\t\t\tclasses = classes.join(' ')\n\t\t\t\t// #endif\n\t\t\t\treturn classes\n\t\t\t},\n\t\t\ticonWrapStyle() {\n\t\t\t\t// checkbox的整体样式\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'\n\t\t\t\tstyle.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor\n\t\t\t\tstyle.width = uni.$u.addUnit(this.elSize)\n\t\t\t\tstyle.height = uni.$u.addUnit(this.elSize)\n\t\t\t\t// 如果是图标在右边的话，移除它的右边距\n\t\t\t\tif (this.parentData.iconPlacement === 'right') {\n\t\t\t\t\tstyle.marginRight = 0\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tcheckboxStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tif (this.parentData.borderBottom && this.parentData.placement === 'row') {\n\t\t\t\t\tuni.$u.error('检测到您将borderBottom设置为true，需要同时将u-checkbox-group的placement设置为column才有效')\n\t\t\t\t}\n\t\t\t\t// 当父组件设置了显示下边框并且排列形式为纵向时，给内容和边框之间加上一定间隔\n\t\t\t\tif (this.parentData.borderBottom && this.parentData.placement === 'column') {\n\t\t\t\t\tstyle.paddingBottom = '8px'\n\t\t\t\t}\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 支付宝小程序不支持provide/inject，所以使用这个方法获取整个父组件，在created定义，避免循环引用\n\t\t\t\tthis.updateParentData()\n\t\t\t\tif (!this.parent) {\n\t\t\t\t\tuni.$u.error('u-checkbox必须搭配u-checkbox-group组件使用')\n\t\t\t\t}\n\t\t\t\t// 设置初始化时，是否默认选中的状态，父组件u-checkbox-group的value可能是array，所以额外判断\n\t\t\t\tif (this.checked) {\n\t\t\t\t\tthis.isChecked = true\n\t\t\t\t} else if (uni.$u.test.array(this.parentData.value)) {\n\t\t\t\t\t// 查找数组是是否存在this.name元素值\n\t\t\t\t\tthis.isChecked = this.parentData.value.some(item => {\n\t\t\t\t\t\treturn item === this.name\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t\tupdateParentData() {\n\t\t\t\tthis.getParentData('u-checkbox-group')\n\t\t\t},\n\t\t\t// 横向两端排列时，点击组件即可触发选中事件\n\t\t\twrapperClickHandler(e) {\n\t\t\t\tthis.parentData.iconPlacement === 'right' && this.iconClickHandler(e)\n\t\t\t},\n\t\t\t// 点击图标\n\t\t\ticonClickHandler(e) {\n\t\t\t\tthis.preventEvent(e)\n\t\t\t\t// 如果整体被禁用，不允许被点击\n\t\t\t\tif (!this.elDisabled) {\n\t\t\t\t\tthis.setRadioCheckedStatus()\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 点击label\n\t\t\tlabelClickHandler(e) {\n\t\t\t\tthis.preventEvent(e)\n\t\t\t\t// 如果按钮整体被禁用或者label被禁用，则不允许点击文字修改状态\n\t\t\t\tif (!this.elLabelDisabled && !this.elDisabled) {\n\t\t\t\t\tthis.setRadioCheckedStatus()\n\t\t\t\t}\n\t\t\t},\n\t\t\temitEvent() {\n\t\t\t\tthis.$emit('change', this.isChecked)\n\t\t\t\t// 尝试调用u-form的验证方法，进行一定延迟，否则微信小程序更新可能会不及时\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tuni.$u.formValidate(this, 'change')\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 改变组件选中状态\n\t\t\t// 这里的改变的依据是，更改本组件的checked值为true，同时通过父组件遍历所有u-checkbox实例\n\t\t\t// 将本组件外的其他u-checkbox的checked都设置为false(都被取消选中状态)，因而只剩下一个为选中状态\n\t\t\tsetRadioCheckedStatus() {\n\t\t\t\t// 将本组件标记为与原来相反的状态\n\t\t\t\tthis.isChecked = !this.isChecked\n\t\t\t\tthis.emitEvent()\n\t\t\t\ttypeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)\n\t\t\t}\n\t\t},\n\t\twatch:{\n\t\t\tchecked(){\n\t\t\t\tthis.isChecked = this.checked\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-checkbox-icon-wrap-margin-right:6px !default;\n\t$u-checkbox-icon-wrap-font-size:6px !default;\n\t$u-checkbox-icon-wrap-border-width:1px !default;\n\t$u-checkbox-icon-wrap-border-color:#c8c9cc !default;\n\t$u-checkbox-icon-wrap-icon-line-height:0 !default;\n\t$u-checkbox-icon-wrap-circle-border-radius:100% !default;\n\t$u-checkbox-icon-wrap-square-border-radius:3px !default;\n\t$u-checkbox-icon-wrap-checked-color:#fff !default;\n\t$u-checkbox-icon-wrap-checked-background-color:red !default;\n\t$u-checkbox-icon-wrap-checked-border-color:#2979ff !default;\n\t$u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;\n\t$u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;\n\t$u-checkbox-label-margin-left:5px !default;\n\t$u-checkbox-label-margin-right:12px !default;\n\t$u-checkbox-label-color:$u-content-color !default;\n\t$u-checkbox-label-font-size:15px !default;\n\t$u-checkbox-label-disabled-color:#c8c9cc !default;\n\n\t.u-checkbox {\n\t\t/* #ifndef APP-NVUE */\n\t\t@include flex(row);\n\t\t/* #endif */\n\t\toverflow: hidden;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\n\t\t&-label--left {\n\t\t\tflex-direction: row\n\t\t}\n\n\t\t&-label--right {\n\t\t\tflex-direction: row-reverse;\n\t\t\tjustify-content: space-between\n\t\t}\n\n\t\t&__icon-wrap {\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tbox-sizing: border-box;\n\t\t\t// nvue下，border-color过渡有问题\n\t\t\ttransition-property: border-color, background-color, color;\n\t\t\ttransition-duration: 0.2s;\n\t\t\t/* #endif */\n\t\t\tcolor: $u-content-color;\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tcolor: transparent;\n\t\t\ttext-align: center;\n\t\t\tmargin-right: $u-checkbox-icon-wrap-margin-right;\n\n\t\t\tfont-size: $u-checkbox-icon-wrap-font-size;\n\t\t\tborder-width: $u-checkbox-icon-wrap-border-width;\n\t\t\tborder-color: $u-checkbox-icon-wrap-border-color;\n\t\t\tborder-style: solid;\n\n\t\t\t/* #ifdef MP-TOUTIAO */\n\t\t\t// 头条小程序兼容性问题，需要设置行高为0，否则图标偏下\n\t\t\t&__icon {\n\t\t\t\tline-height: $u-checkbox-icon-wrap-icon-line-height;\n\t\t\t}\n\n\t\t\t/* #endif */\n\n\t\t\t&--circle {\n\t\t\t\tborder-radius: $u-checkbox-icon-wrap-circle-border-radius;\n\t\t\t}\n\n\t\t\t&--square {\n\t\t\t\tborder-radius: $u-checkbox-icon-wrap-square-border-radius;\n\t\t\t}\n\n\t\t\t&--checked {\n\t\t\t\tcolor: $u-checkbox-icon-wrap-checked-color;\n\t\t\t\tbackground-color: $u-checkbox-icon-wrap-checked-background-color;\n\t\t\t\tborder-color: $u-checkbox-icon-wrap-checked-border-color;\n\t\t\t}\n\n\t\t\t&--disabled {\n\t\t\t\tbackground-color: $u-checkbox-icon-wrap-disabled-background-color !important;\n\t\t\t}\n\n\t\t\t&--disabled--checked {\n\t\t\t\tcolor: $u-checkbox-icon-wrap-disabled-checked-color !important;\n\t\t\t}\n\t\t}\n\n\t\t&__label {\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tword-wrap: break-word;\n\t\t\t/* #endif */\n\t\t\tmargin-left: $u-checkbox-label-margin-left;\n\t\t\tmargin-right: $u-checkbox-label-margin-right;\n\t\t\tcolor: $u-checkbox-label-color;\n\t\t\tfont-size: $u-checkbox-label-font-size;\n\n\t\t\t&--disabled {\n\t\t\t\tcolor: $u-checkbox-label-disabled-color;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-checkbox-group/props.js",
    "content": "export default {\n    props: {\n        // 标识符\n        name: {\n            type: String,\n            default: uni.$u.props.checkboxGroup.name\n        },\n        // 绑定的值\n        value: {\n            type: Array,\n            default: uni.$u.props.checkboxGroup.value\n        },\n        // 形状，circle-圆形，square-方形\n        shape: {\n            type: String,\n            default: uni.$u.props.checkboxGroup.shape\n        },\n        // 是否禁用全部checkbox\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.checkboxGroup.disabled\n        },\n\n        // 选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\n        activeColor: {\n            type: String,\n            default: uni.$u.props.checkboxGroup.activeColor\n        },\n        // 未选中的颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.checkboxGroup.inactiveColor\n        },\n\n        // 整个组件的尺寸，默认px\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.checkboxGroup.size\n        },\n        // 布局方式，row-横向，column-纵向\n        placement: {\n            type: String,\n            default: uni.$u.props.checkboxGroup.placement\n        },\n        // label的字体大小，px单位\n        labelSize: {\n            type: [String, Number],\n            default: uni.$u.props.checkboxGroup.labelSize\n        },\n        // label的字体颜色\n        labelColor: {\n            type: [String],\n            default: uni.$u.props.checkboxGroup.labelColor\n        },\n        // 是否禁止点击文本操作\n        labelDisabled: {\n            type: Boolean,\n            default: uni.$u.props.checkboxGroup.labelDisabled\n        },\n        // 图标颜色\n        iconColor: {\n            type: String,\n            default: uni.$u.props.checkboxGroup.iconColor\n        },\n        // 图标的大小，单位px\n        iconSize: {\n            type: [String, Number],\n            default: uni.$u.props.checkboxGroup.iconSize\n        },\n        // 勾选图标的对齐方式，left-左边，right-右边\n        iconPlacement: {\n            type: String,\n            default: uni.$u.props.checkboxGroup.iconPlacement\n        },\n        // 竖向配列时，是否显示下划线\n        borderBottom: {\n            type: Boolean,\n            default: uni.$u.props.checkboxGroup.borderBottom\n        }\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-checkbox-group/u-checkbox-group.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-checkbox-group\"\n\t    :class=\"bemClass\"\n\t>\n\t\t<slot></slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * checkboxGroup 复选框组\n\t * @description 复选框组件一般用于需要多个选择的场景，该组件功能完整，使用方便\n\t * @tutorial https://www.uviewui.com/components/checkbox.html\n\t * @property {String}\t\t\tname\t\t\t标识符 \n\t * @property {Array}\t\t\tvalue\t\t\t绑定的值\n\t * @property {String}\t\t\tshape\t\t\t形状，circle-圆形，square-方形 （默认 'square' ）\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用全部checkbox （默认 false ）\n\t * @property {String}\t\t\tactiveColor\t\t选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值 （默认 '#2979ff' ）\n\t * @property {String}\t\t\tinactiveColor\t未选中的颜色 （默认 '#c8c9cc' ）\n\t * @property {String | Number}\tsize\t\t\t整个组件的尺寸 单位px （默认 18 ）\n\t * @property {String}\t\t\tplacement\t\t布局方式，row-横向，column-纵向 （默认 'row' ）\n\t * @property {String | Number}\tlabelSize\t\tlabel的字体大小，px单位  （默认 14 ）\n\t * @property {String}\t\t\tlabelColor\t\tlabel的字体颜色 （默认 '#303133' ）\n\t * @property {Boolean}\t\t\tlabelDisabled\t是否禁止点击文本操作 (默认 false )\n\t * @property {String}\t\t\ticonColor\t\t图标颜色 （默认 '#ffffff' ）\n\t * @property {String | Number}\ticonSize\t\t图标的大小，单位px （默认 12 ）\n\t * @property {String}\t\t\ticonPlacement\t勾选图标的对齐方式，left-左边，right-右边  （默认 'left' ）\n\t * @property {Boolean}\t\t\tborderBottom\tplacement为row时，是否显示下边框 （默认 false ）\n\t * @event {Function}\tchange\t任一个checkbox状态发生变化时触发，回调为一个对象\n\t * @event {Function}\tinput\t修改通过v-model绑定的值时触发，回调为一个对象\n\t * @example <u-checkbox-group></u-checkbox-group>\n\t */\n\texport default {\n\t\tname: 'u-checkbox-group',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\t// 这里computed的变量，都是子组件u-checkbox需要用到的，由于头条小程序的兼容性差异，子组件无法实时监听父组件参数的变化\n\t\t\t// 所以需要手动通知子组件，这里返回一个parentData变量，供watch监听，在其中去通知每一个子组件重新从父组件(u-checkbox-group)\n\t\t\t// 拉取父组件新的变化后的参数\n\t\t\tparentData() {\n\t\t\t\treturn [this.value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,\n\t\t\t\t\tthis.iconSize, this.borderBottom, this.placement\n\t\t\t\t]\n\t\t\t},\n\t\t\tbemClass() {\n\t\t\t\t// this.bem为一个computed变量，在mixin中\n\t\t\t\treturn this.bem('checkbox-group', ['placement'])\n\t\t\t},\n\t\t},\n\t\twatch: {\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\n\t\t\tparentData() {\n\t\t\t\tif (this.children.length) {\n\t\t\t\t\tthis.children.map(child => {\n\t\t\t\t\t\t// 判断子组件(u-checkbox)如果有init方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\n\t\t\t\t\t\ttypeof(child.init) === 'function' && child.init()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.children = []\n\t\t},\n\t\tmethods: {\n\t\t\t// 将其他的checkbox设置为未选中的状态\n\t\t\tunCheckedOther(childInstance) {\n\t\t\t\tconst values = []\n\t\t\t\tthis.children.map(child => {\n\t\t\t\t\t// 将被选中的checkbox，放到数组中返回\n\t\t\t\t\tif (child.isChecked) {\n\t\t\t\t\t\tvalues.push(child.name)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t// 发出事件\n\t\t\t\tthis.$emit('change', values)\n\t\t\t\t// 修改通过v-model绑定的值\n\t\t\t\tthis.$emit('input', values)\n\t\t\t},\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-checkbox-group {\n\n\t\t&--row {\n\t\t\t@include flex;\n\t\t}\n\n\t\t&--column {\n\t\t\t@include flex(column);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-circle-progress/props.js",
    "content": "export default {\n    props: {\n        percentage: {\n            type: [String, Number],\n            default: uni.$u.props.circleProgress.percentage\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-circle-progress/u-circle-progress.vue",
    "content": "<template>\n\t<view class=\"u-circle-progress\">\n\t\t<view class=\"u-circle-progress__left\">\n\t\t\t<view\n\t\t\t    class=\"u-circle-progress__left__circle\"\n\t\t\t    :style=\"[leftSyle]\"\n\t\t\t    ref=\"left-circle\"\n\t\t\t>\n\n\t\t\t</view>\n\t\t</view>\n\t\t<view\n\t\t    class=\"u-circle-progress__right\"\n\t\t>\n\t\t\t<view\n\t\t\t    class=\"u-circle-progress__right__circle\"\n\t\t\t    ref=\"right-circle\"\n\t\t\t\t:style=\"[rightSyle]\"\n\t\t\t>\n\n\t\t\t</view>\n\t\t</view>\n\t\t<view class=\"u-circle-progress__circle\">\n\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst animation = uni.requireNativePlugin('animation')\n\t// #endif\n\t/**\n\t * CircleProgress 圆形进度条 TODO: 待完善 \n\t * @description 展示操作或任务的当前进度，比如上传文件，是一个圆形的进度环。\n\t * @tutorial https://www.uviewui.com/components/circleProgress.html\n\t * @property {String | Number}\tpercentage\t圆环进度百分比值，为数值类型，0-100 (默认 30 )\n\t * @example\n\t */\n\texport default {\n\t\tname: 'u-circle-progress',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tleftBorderColor: 'rgb(200, 200, 200)',\n\t\t\t\trightBorderColor: 'rgb(200, 200, 200)',\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tleftSyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.borderTopColor = this.leftBorderColor\n\t\t\t\tstyle.borderRightColor = this.leftBorderColor\n\t\t\t\treturn style\n\t\t\t},\n\t\t\trightSyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.borderLeftColor = this.rightBorderColor\n\t\t\t\tstyle.borderBottomColor = this.rightBorderColor\n\t\t\t\treturn style\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tuni.$u.sleep().then(() => {\n\t\t\t\tthis.rightBorderColor = 'rgb(66, 185, 131)'\n\t\t\t\t// this.init()\n\t\t\t})\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tanimation.transition(this.$refs['right-circle'].ref, {\n\t\t\t\t\tstyles: {\n\t\t\t\t\t\ttransform: 'rotate(45deg)',\n\t\t\t\t\t\ttransformOrigin: 'center center'\n\t\t\t\t\t},\n\t\t\t\t}, () => {\n\t\t\t\t\tthis.rightBorderColor = 'rgb(66, 185, 131)'\n\t\t\t\t\t// animation.transition(this.$refs['right-circle'].ref, {\n\t\t\t\t\t// \tstyles: {\n\t\t\t\t\t// \t\ttransform: 'rotate(225deg)',\n\t\t\t\t\t// \t\ttransformOrigin: 'center center'\n\t\t\t\t\t// \t},\n\t\t\t\t\t// \tduration: 3000,\n\t\t\t\t\t// }, () => {\n\t\t\t\t\t// \tanimation.transition(this.$refs['left-circle'].ref, {\n\t\t\t\t\t// \t\tstyles: {\n\t\t\t\t\t// \t\t\ttransform: 'rotate(45deg)',\n\t\t\t\t\t// \t\t\ttransformOrigin: 'center center'\n\t\t\t\t\t// \t\t},\n\t\t\t\t\t// \t}, () => {\n\t\t\t\t\t// \t\tthis.leftBorderColor = 'rgb(66, 185, 131)'\n\t\t\t\t\t// \t\tanimation.transition(this.$refs['left-circle'].ref, {\n\t\t\t\t\t// \t\t\tstyles: {\n\t\t\t\t\t// \t\t\t\ttransform: 'rotate(225deg)',\n\t\t\t\t\t// \t\t\t\ttransformOrigin: 'center center'\n\t\t\t\t\t// \t\t\t},\n\t\t\t\t\t// \t\t\tduration: 1500,\n\t\t\t\t\t// \t\t}, () => {\n\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}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-circle-progress {\n\t\t@include flex(row);\n\t\tposition: relative;\n\t\tborder-radius: 100px;\n\t\theight: 100px;\n\t\twidth: 100px;\n\t\t// transform: rotate(0deg);\n\t\t// background-color: rgb(66, 185, 131);\n\t\tbackground-color: rgb(200, 200, 200);\n\t\toverflow: hidden;\n\t\tjustify-content: space-between;\n\n\t\t&__circle {\n\t\t\tborder-radius: 100px;\n\t\t\theight: 90px;\n\t\t\twidth: 90px;\n\t\t\ttransform: translate(-50%, -50%);\n\t\t\tbackground-color: rgb(255, 255, 255);\n\t\t\tleft: 50px;\n\t\t\ttop: 50px;\n\t\t\tposition: absolute;\n\t\t}\n\n\t\t&__left {\n\t\t\tposition: absolute;\n\t\t\tleft: 0;\n\t\t\twidth: 50px;\n\t\t\theight: 100px;\n\t\t\toverflow: hidden;\n\t\t\tbox-sizing: border-box;\n\t\t\t// background-color: rgb(66, 185, 131);\n\t\t\t// background-color: rgb(200, 200, 200);\n\t\t\t// transform-origin: left center;\n\n\t\t\t&__circle {\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\t// background-color: red;\n\t\t\t\tborder-left-color: transparent;\n\t\t\t\tborder-bottom-color: transparent;\n\t\t\t\tborder-top-left-radius: 50px;\n\t\t\t\tborder-top-right-radius: 50px;\n\t\t\t\tborder-bottom-right-radius: 50px;\n\t\t\t\t// border-left-color: rgb(66, 185, 131);\n\t\t\t\t// border-bottom-color: rgb(66, 185, 131);\n\t\t\t\tborder-top-color: rgb(66, 185, 131);\n\t\t\t\tborder-right-color: rgb(66, 185, 131);\n\t\t\t\tborder-width: 5px;\n\t\t\t\twidth: 100px;\n\t\t\t\theight: 100px;\n\t\t\t\ttransform: rotate(225deg);\n\t\t\t\t// border-radius: 100px;\n\t\t\t}\n\t\t}\n\n\t\t&__right {\n\t\t\tposition: absolute;\n\t\t\tright: 0;\n\t\t\twidth: 50px;\n\t\t\theight: 100px;\n\t\t\toverflow: hidden;\n\n\t\t\t&__circle {\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 0;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\t// background-color: red;\n\t\t\t\tborder-top-color: transparent;\n\t\t\t\tborder-right-color: transparent;\n\t\t\t\tborder-top-left-radius: 50px;\n\t\t\t\tborder-bottom-left-radius: 50px;\n\t\t\t\tborder-bottom-right-radius: 50px;\n\t\t\t\t// border-left-color: rgb(66, 185, 131);\n\t\t\t\t// border-bottom-color: rgb(66, 185, 131);\n\t\t\t\tborder-left-color: rgb(200, 200, 200);\n\t\t\t\tborder-bottom-color: rgb(200, 200, 200);\n\t\t\t\tborder-width: 5px;\n\t\t\t\twidth: 100px;\n\t\t\t\theight: 100px;\n\t\t\t\ttransform: rotate(45deg);\n\t\t\t\ttransform-origin: center center;\n\t\t\t\t// border-radius: 100px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-code/props.js",
    "content": "export default {\n    props: {\n        // 倒计时总秒数\n        seconds: {\n            type: [String, Number],\n            default: uni.$u.props.code.seconds\n        },\n        // 尚未开始时提示\n        startText: {\n            type: String,\n            default: uni.$u.props.code.startText\n        },\n        // 正在倒计时中的提示\n        changeText: {\n            type: String,\n            default: uni.$u.props.code.changeText\n        },\n        // 倒计时结束时的提示\n        endText: {\n            type: String,\n            default: uni.$u.props.code.endText\n        },\n        // 是否在H5刷新或各端返回再进入时继续倒计时\n        keepRunning: {\n            type: Boolean,\n            default: uni.$u.props.code.keepRunning\n        },\n        // 为了区分多个页面，或者一个页面多个倒计时组件本地存储的继续倒计时变了\n        uniqueKey: {\n            type: String,\n            default: uni.$u.props.code.uniqueKey\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-code/u-code.vue",
    "content": "<template>\n\t<view class=\"u-code\">\n\t\t<!-- 此组件功能由js完成，无需写html逻辑 -->\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Code 验证码输入框\n\t * @description 考虑到用户实际发送验证码的场景，可能是一个按钮，也可能是一段文字，提示语各有不同，所以本组件 不提供界面显示，只提供提示语，由用户将提示语嵌入到具体的场景\n\t * @tutorial https://www.uviewui.com/components/code.html\n\t * @property {String | Number}\tseconds\t\t\t倒计时所需的秒数（默认 60 ）\n\t * @property {String}\t\t\tstartText\t\t开始前的提示语，见官网说明（默认 '获取验证码' ）\n\t * @property {String}\t\t\tchangeText\t\t倒计时期间的提示语，必须带有字母\"x\"，见官网说明（默认 'X秒重新获取' ）\n\t * @property {String}\t\t\tendText\t\t\t倒计结束的提示语，见官网说明（默认 '重新获取' ）\n\t * @property {Boolean}\t\t\tkeepRunning\t\t是否在H5刷新或各端返回再进入时继续倒计时（ 默认false ）\n\t * @property {String}\t\t\tuniqueKey\t\t为了区分多个页面，或者一个页面多个倒计时组件本地存储的继续倒计时变了\n\t *\n\t * @event {Function}\tchange\t倒计时期间，每秒触发一次\n\t * @event {Function}\tstart\t开始倒计时触发\n\t * @event {Function}\tend\t\t结束倒计时触发\n\t * @example <u-code ref=\"uCode\" @change=\"codeChange\" seconds=\"20\"></u-code>\n\t */\n\texport default {\n\t\tname: \"u-code\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tsecNum: this.seconds,\n\t\t\t\ttimer: null,\n\t\t\t\tcanGetCode: true, // 是否可以执行验证码操作\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.checkKeepRunning()\n\t\t},\n\t\twatch: {\n\t\t\tseconds: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(n) {\n\t\t\t\t\tthis.secNum = n\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tcheckKeepRunning() {\n\t\t\t\t// 获取上一次退出页面(H5还包括刷新)时的时间戳，如果没有上次的保存，此值可能为空\n\t\t\t\tlet lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))\n\t\t\t\tif(!lastTimestamp) return this.changeEvent(this.startText)\n\t\t\t\t// 当前秒的时间戳\n\t\t\t\tlet nowTimestamp = Math.floor((+ new Date()) / 1000)\n\t\t\t\t// 判断当前的时间戳，是否小于上一次的本该按设定结束，却提前结束的时间戳\n\t\t\t\tif(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {\n\t\t\t\t\t// 剩余尚未执行完的倒计秒数\n\t\t\t\t\tthis.secNum = lastTimestamp - nowTimestamp\n\t\t\t\t\t// 清除本地保存的变量\n\t\t\t\t\tuni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')\n\t\t\t\t\t// 开始倒计时\n\t\t\t\t\tthis.start()\n\t\t\t\t} else {\n\t\t\t\t\t// 如果不存在需要继续上一次的倒计时，执行正常的逻辑\n\t\t\t\t\tthis.changeEvent(this.startText)\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 开始倒计时\n\t\t\tstart() {\n\t\t\t\t// 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱\n\t\t\t\tif(this.timer) {\n\t\t\t\t\tclearInterval(this.timer)\n\t\t\t\t\tthis.timer = null\n\t\t\t\t}\n\t\t\t\tthis.$emit('start')\n\t\t\t\tthis.canGetCode = false\n\t\t\t\t// 这里放这句，是为了一开始时就提示，否则要等setInterval的1秒后才会有提示\n\t\t\t\tthis.changeEvent(this.changeText.replace(/x|X/, this.secNum))\n\t\t\t\tthis.timer = setInterval(() => {\n\t\t\t\t\tif (--this.secNum) {\n\t\t\t\t\t\t// 用当前倒计时的秒数替换提示字符串中的\"x\"字母\n\t\t\t\t\t\tthis.changeEvent(this.changeText.replace(/x|X/, this.secNum))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclearInterval(this.timer)\n\t\t\t\t\t\tthis.timer = null\n\t\t\t\t\t\tthis.changeEvent(this.endText)\n\t\t\t\t\t\tthis.secNum = this.seconds\n\t\t\t\t\t\tthis.$emit('end')\n\t\t\t\t\t\tthis.canGetCode = true\n\t\t\t\t\t}\n\t\t\t\t}, 1000)\n        this.setTimeToStorage()\n      },\n\t\t\t// 重置，可以让用户再次获取验证码\n\t\t\treset() {\n\t\t\t\tthis.canGetCode = true\n\t\t\t\tclearInterval(this.timer)\n\t\t\t\tthis.secNum = this.seconds\n\t\t\t\tthis.changeEvent(this.endText)\n\t\t\t},\n\t\t\tchangeEvent(text) {\n\t\t\t\tthis.$emit('change', text)\n\t\t\t},\n\t\t\t// 保存时间戳，为了防止倒计时尚未结束，H5刷新或者各端的右上角返回上一页再进来\n\t\t\tsetTimeToStorage() {\n\t\t\t\tif(!this.keepRunning || !this.timer) return\n\t\t\t\t// 记录当前的时间戳，为了下次进入页面，如果还在倒计时内的话，继续倒计时\n\t\t\t\t// 倒计时尚未结束，结果大于0；倒计时已经开始，就会小于初始值，如果等于初始值，说明没有开始倒计时，无需处理\n\t\t\t\tif(this.secNum > 0 && this.secNum <= this.seconds) {\n\t\t\t\t\t// 获取当前时间戳(+ new Date()为特殊写法)，除以1000变成秒，再去除小数部分\n\t\t\t\t\tlet nowTimestamp = Math.floor((+ new Date()) / 1000)\n\t\t\t\t\t// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数\n\t\t\t\t\tuni.setStorage({\n\t\t\t\t\t\tkey: this.uniqueKey + '_$uCountDownTimestamp',\n\t\t\t\t\t\tdata: nowTimestamp + Number(this.secNum)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 组件销毁的时候，清除定时器，否则定时器会继续存在，系统不会自动清除\n\t\tbeforeDestroy() {\n\t\t\tthis.setTimeToStorage()\n\t\t\tclearTimeout(this.timer)\n\t\t\tthis.timer = null\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-code-input/props.js",
    "content": "export default {\n    props: {\n\t\t// 键盘弹起时，是否自动上推页面\n\t\tadjustPosition: {\n\t\t\ttype: Boolean,\n            default: uni.$u.props.codeInput.adjustPosition\n\t\t},\n        // 最大输入长度\n        maxlength: {\n            type: [String, Number],\n            default: uni.$u.props.codeInput.maxlength\n        },\n        // 是否用圆点填充\n        dot: {\n            type: Boolean,\n            default: uni.$u.props.codeInput.dot\n        },\n        // 显示模式，box-盒子模式，line-底部横线模式\n        mode: {\n            type: String,\n            default: uni.$u.props.codeInput.mode\n        },\n        // 是否细边框\n        hairline: {\n            type: Boolean,\n            default: uni.$u.props.codeInput.hairline\n        },\n        // 字符间的距离\n        space: {\n            type: [String, Number],\n            default: uni.$u.props.codeInput.space\n        },\n        // 预置值\n        value: {\n            type: [String, Number],\n            default: uni.$u.props.codeInput.value\n        },\n        // 是否自动获取焦点\n        focus: {\n            type: Boolean,\n            default: uni.$u.props.codeInput.focus\n        },\n        // 字体是否加粗\n        bold: {\n            type: Boolean,\n            default: uni.$u.props.codeInput.bold\n        },\n        // 字体颜色\n        color: {\n            type: String,\n            default: uni.$u.props.codeInput.color\n        },\n        // 字体大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.codeInput.fontSize\n        },\n        // 输入框的大小，宽等于高\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.codeInput.size\n        },\n        // 是否隐藏原生键盘，如果想用自定义键盘的话，需设置此参数为true\n        disabledKeyboard: {\n            type: Boolean,\n            default: uni.$u.props.codeInput.disabledKeyboard\n        },\n        // 边框和线条颜色\n        borderColor: {\n            type: String,\n            default: uni.$u.props.codeInput.borderColor\n        },\n\t\t// 是否禁止输入\".\"符号\n\t\tdisabledDot: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.codeInput.disabledDot\n\t\t}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-code-input/u-code-input.vue",
    "content": "<template>\n\t<view class=\"u-code-input\">\n\t\t<view\n\t\t\tclass=\"u-code-input__item\"\n\t\t\t:style=\"[itemStyle(index)]\"\n\t\t\tv-for=\"(item, index) in codeLength\"\n\t\t\t:key=\"index\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-code-input__item__dot\"\n\t\t\t\tv-if=\"dot && codeArray.length > index\"\n\t\t\t></view>\n\t\t\t<text\n\t\t\t\tv-else\n\t\t\t\t:style=\"{\n\t\t\t\t\tfontSize: $u.addUnit(fontSize),\n\t\t\t\t\tfontWeight: bold ? 'bold' : 'normal',\n\t\t\t\t\tcolor: color\n\t\t\t\t}\"\n\t\t\t>{{codeArray[index]}}</text>\n\t\t\t<view\n\t\t\t\tclass=\"u-code-input__item__line\"\n\t\t\t\tv-if=\"mode === 'line'\"\n\t\t\t\t:style=\"[lineStyle]\"\n\t\t\t></view>\n\t\t\t<!-- #ifndef APP-PLUS -->\n\t\t\t<view v-if=\"isFocus && codeArray.length === index\" :style=\"{backgroundColor: color}\" class=\"u-code-input__item__cursor\"></view>\n\t\t\t<!-- #endif -->\n\t\t</view>\n\t\t<input\n\t\t\t:disabled=\"disabledKeyboard\"\n\t\t\ttype=\"number\"\n\t\t\t:focus=\"focus\"\n\t\t\t:value=\"inputValue\"\n\t\t\t:maxlength=\"maxlength\"\n\t\t\t:adjustPosition=\"adjustPosition\"\n\t\t\tclass=\"u-code-input__input\"\n\t\t\t@input=\"inputHandler\"\n\t\t\t:style=\"{\n\t\t\t\theight: $u.addUnit(size) \n\t\t\t}\"\n\t\t\t@focus=\"isFocus = true\"\n\t\t\t@blur=\"isFocus = false\"\n\t\t/>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * CodeInput 验证码输入\n\t * @description 该组件一般用于验证用户短信验证码的场景，也可以结合uView的键盘组件使用\n\t * @tutorial https://www.uviewui.com/components/codeInput.html\n\t * @property {String | Number}\tmaxlength\t\t\t最大输入长度 （默认 6 ）\n\t * @property {Boolean}\t\t\tdot\t\t\t\t\t是否用圆点填充 （默认 false ）\n\t * @property {String}\t\t\tmode\t\t\t\t显示模式，box-盒子模式，line-底部横线模式 （默认 'box' ）\n\t * @property {Boolean}\t\t\thairline\t\t\t是否细边框 （默认 false ）\n\t * @property {String | Number}\tspace\t\t\t\t字符间的距离 （默认 10 ）\n\t * @property {String | Number}\tvalue\t\t\t\t预置值\n\t * @property {Boolean}\t\t\tfocus\t\t\t\t是否自动获取焦点 （默认 false ）\n\t * @property {Boolean}\t\t\tbold\t\t\t\t字体和输入横线是否加粗 （默认 false ）\n\t * @property {String}\t\t\tcolor\t\t\t\t字体颜色 （默认 '#606266' ）\n\t * @property {String | Number}\tfontSize\t\t\t字体大小，单位px （默认 18 ）\n\t * @property {String | Number}\tsize\t\t\t\t输入框的大小，宽等于高 （默认 35 ）\n\t * @property {Boolean}\t\t\tdisabledKeyboard\t是否隐藏原生键盘，如果想用自定义键盘的话，需设置此参数为true （默认 false ）\n\t * @property {String}\t\t\tborderColor\t\t\t边框和线条颜色 （默认 '#c9cacc' ）\n\t * @property {Boolean}\t\t\tdisabledDot\t\t\t是否禁止输入\".\"符号 （默认 true ）\n\t * \n\t * @event {Function}\tchange\t输入内容发生改变时触发，具体见上方说明\t\t\tvalue：当前输入的值\n\t * @event {Function}\tfinish\t输入字符个数达maxlength值时触发，见上方说明\tvalue：当前输入的值\n\t * @example\t<u-code-input v-model=\"value4\" :focus=\"true\"></u-code-input>\n\t */\n\texport default {\n\t\tname: 'u-code-input',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tinputValue: '',\n\t\t\t\tisFocus: this.focus\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tvalue: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(val) {\n\t\t\t\t\t// 转为字符串，超出部分截掉\n\t\t\t\t\tthis.inputValue = String(val).substring(0, this.maxlength)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tcomputed: {\n\t\t\t// 根据长度，循环输入框的个数，因为头条小程序数值不能用于v-for\n\t\t\tcodeLength() {\n\t\t\t\treturn new Array(Number(this.maxlength))\n\t\t\t},\n\t\t\t// 循环item的样式\n\t\t\titemStyle() {\n\t\t\t\treturn index => {\n\t\t\t\t\tconst addUnit = uni.$u.addUnit\n\t\t\t\t\tconst style = {\n\t\t\t\t\t\twidth: addUnit(this.size),\n\t\t\t\t\t\theight: addUnit(this.size)\n\t\t\t\t\t}\n\t\t\t\t\t// 盒子模式下，需要额外进行处理\n\t\t\t\t\tif (this.mode === 'box') {\n\t\t\t\t\t\t// 设置盒子的边框，如果是细边框，则设置为0.5px宽度\n\t\t\t\t\t\tstyle.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`\n\t\t\t\t\t\t// 如果盒子间距为0的话\n\t\t\t\t\t\tif (uni.$u.getPx(this.space) === 0) {\n\t\t\t\t\t\t\t// 给第一和最后一个盒子设置圆角\n\t\t\t\t\t\t\tif (index === 0) {\n\t\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\n\t\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (index === this.codeLength.length - 1) {\n\t\t\t\t\t\t\t\tstyle.borderTopRightRadius = '3px'\n\t\t\t\t\t\t\t\tstyle.borderBottomRightRadius = '3px'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 最后一个盒子的右边框需要保留\n\t\t\t\t\t\t\tif (index !== this.codeLength.length - 1) {\n\t\t\t\t\t\t\t\tstyle.borderRight = 'none'\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\tif (index !== this.codeLength.length - 1) {\n\t\t\t\t\t\t// 设置验证码字符之间的距离，通过margin-right设置，最后一个字符，无需右边框\n\t\t\t\t\t\tstyle.marginRight = addUnit(this.space)\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// 最后一个盒子的有边框需要保留\n\t\t\t\t\t\tstyle.marginRight = 0\n\t\t\t\t\t}\n\n\t\t\t\t\treturn style\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 将输入的值，转为数组，给item历遍时，根据当前的索引显示数组的元素\n\t\t\tcodeArray() {\n\t\t\t\treturn String(this.inputValue).split('')\n\t\t\t},\n\t\t\t// 下划线模式下，横线的样式\n\t\t\tlineStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.height = this.hairline ? '2px' : '4px'\n\t\t\t\tstyle.width = uni.$u.addUnit(this.size)\n\t\t\t\t// 线条模式下，背景色即为边框颜色\n\t\t\t\tstyle.backgroundColor = this.borderColor\n\t\t\t\treturn style\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 监听输入框的值发生变化\n\t\t\tinputHandler(e) {\n\t\t\t\tconst value = e.detail.value\n\t\t\t\tthis.inputValue = value\n\t\t\t\t// 是否允许输入“.”符号\n\t\t\t\tif(this.disabledDot) {\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.inputValue = value.replace('.', '')\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// 未达到maxlength之前，发送change事件，达到后发送finish事件\n\t\t\t\tthis.$emit('change', value)\n\t\t\t\t// 修改通过v-model双向绑定的值\n\t\t\t\tthis.$emit('input', value)\n\t\t\t\t// 达到用户指定输入长度时，发出完成事件\n\t\t\t\tif (String(value).length >= Number(this.maxlength)) {\n\t\t\t\t\tthis.$emit('finish', value)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-code-input-cursor-width: 1px;\n\t$u-code-input-cursor-height: 40%;\n\t$u-code-input-cursor-animation-duration: 1s;\n\t$u-code-input-cursor-animation-name: u-cursor-flicker;\n\n\t.u-code-input {\n\t\t@include flex;\n\t\tposition: relative;\n\t\toverflow: hidden;\n\n\t\t&__item {\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\tposition: relative;\n\n\t\t\t&__text {\n\t\t\t\tfont-size: 15px;\n\t\t\t\tcolor: $u-content-color;\n\t\t\t}\n\n\t\t\t&__dot {\n\t\t\t\twidth: 7px;\n\t\t\t\theight: 7px;\n\t\t\t\tborder-radius: 100px;\n\t\t\t\tbackground-color: $u-content-color;\n\t\t\t}\n\n\t\t\t&__line {\n\t\t\t\tposition: absolute;\n\t\t\t\tbottom: 0;\n\t\t\t\theight: 4px;\n\t\t\t\tborder-radius: 100px;\n\t\t\t\twidth: 40px;\n\t\t\t\tbackground-color: $u-content-color;\n\t\t\t}\n\t\t\t/* #ifndef APP-PLUS */\n\t\t\t&__cursor {\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 50%;\n\t\t\t\tleft: 50%;\n\t\t\t\ttransform: translate(-50%,-50%);\n\t\t\t\twidth: $u-code-input-cursor-width;\n\t\t\t\theight: $u-code-input-cursor-height;\n\t\t\t\tanimation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;\n\t\t\t}\n\t\t\t/* #endif */\n\t\t\t\n\t\t}\n\n\t\t&__input {\n\t\t\t// 之所以需要input输入框，是因为有它才能唤起键盘\n\t\t\t// 这里将它设置为两倍的屏幕宽度，再将左边的一半移出屏幕，为了不让用户看到输入的内容\n\t\t\tposition: absolute;\n\t\t\tleft: -750rpx;\n\t\t\twidth: 1500rpx;\n\t\t\ttop: 0;\n\t\t\tbackground-color: transparent;\n\t\t\ttext-align: left;\n\t\t}\n\t}\n\t\n\t/* #ifndef APP-PLUS */\n\t@keyframes u-cursor-flicker {\n\t\t0% {\n\t\t    opacity: 0;\n\t\t}\n\t\t50% {\n\t\t    opacity: 1;\n\t\t}\n\t\t100% {\n\t\t    opacity: 0;\n\t\t}\n\t}\n\t/* #endif */\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-col/props.js",
    "content": "export default {\n    props: {\n        // 占父容器宽度的多少等分，总分为12份\n        span: {\n            type: [String, Number],\n            default: uni.$u.props.col.span\n        },\n        // 指定栅格左侧的间隔数(总12栏)\n        offset: {\n            type: [String, Number],\n            default: uni.$u.props.col.offset\n        },\n        // 水平排列方式，可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)\n        justify: {\n            type: String,\n            default: uni.$u.props.col.justify\n        },\n        // 垂直对齐方式，可选值为top、center、bottom、stretch\n        align: {\n            type: String,\n            default: uni.$u.props.col.align\n        },\n        // 文字对齐方式\n        textAlign: {\n            type: String,\n            default: uni.$u.props.col.textAlign\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-col/u-col.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-col\"\n\t\tref=\"u-col\"\n\t    :class=\"[\n\t\t\t'u-col-' + span\n\t\t]\"\n\t    :style=\"[colStyle]\"\n\t    @tap=\"clickHandler\"\n\t>\n\t\t<slot></slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * CodeInput 栅格系统的列 \n\t * @description 该组件一般用于Layout 布局 通过基础的 12 分栏，迅速简便地创建布局\n\t * @tutorial https://www.uviewui.com/components/Layout.html\n\t * @property {String | Number}\tspan\t\t栅格占据的列数，总12等份 (默认 12 ) \n\t * @property {String | Number}\toffset\t\t分栏左边偏移，计算方式与span相同 (默认 0 ) \n\t * @property {String}\t\t\tjustify\t\t水平排列方式，可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)  (默认 'start' ) \n\t * @property {String}\t\t\talign\t\t垂直对齐方式，可选值为top、center、bottom、stretch (默认 'stretch' ) \n\t * @property {String}\t\t\ttextAlign\t文字水平对齐方式 (默认 'left' ) \n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * @event {Function}\tclick\tcol被点击，会阻止事件冒泡到row\n\t * @example\t <u-col  span=\"3\" offset=\"3\" > <view class=\"demo-layout bg-purple\"></view> </u-col>\n\t */\n\texport default {\n\t\tname: 'u-col',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\twidth: 0,\n\t\t\t\tparentData: {\n\t\t\t\t\tgutter: 0\n\t\t\t\t},\n\t\t\t\tgridNum: 12\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tuJustify() {\n\t\t\t\tif (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify\n\t\t\t\telse if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify\n\t\t\t\telse return this.justify\n\t\t\t},\n\t\t\tuAlignItem() {\n\t\t\t\tif (this.align == 'top') return 'flex-start'\n\t\t\t\tif (this.align == 'bottom') return 'flex-end'\n\t\t\t\telse return this.align\n\t\t\t},\n\t\t\tcolStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\t// 这里写成\"padding: 0 10px\"的形式是因为nvue的需要\n\t\t\t\t\tpaddingLeft: uni.$u.addUnit(uni.$u.getPx(this.parentData.gutter)/2),\n\t\t\t\t\tpaddingRight: uni.$u.addUnit(uni.$u.getPx(this.parentData.gutter)/2),\n\t\t\t\t\talignItems: this.uAlignItem,\n\t\t\t\t\tjustifyContent: this.uJustify,\n\t\t\t\t\ttextAlign: this.textAlign,\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\t// 在非nvue上，使用百分比形式\n\t\t\t\t\tflex: `0 0 ${100 / this.gridNum * this.span}%`,\n\t\t\t\t\tmarginLeft: 100 / 12 * this.offset + '%',\n\t\t\t\t\t// #endif\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\t// 在nvue上，由于无法使用百分比单位，这里需要获取父组件的宽度，再计算得出该有对应的百分比尺寸\n\t\t\t\t\twidth: uni.$u.addUnit(Math.floor(this.width / this.gridNum * Number(this.span))),\n\t\t\t\t\tmarginLeft: uni.$u.addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))),\n\t\t\t\t\t// #endif\n\t\t\t\t}\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tasync init() {\n\t\t\t\t// 支付宝小程序不支持provide/inject，所以使用这个方法获取整个父组件，在created定义，避免循环引用\n\t\t\t\tthis.updateParentData()\n\t\t\t\tthis.width = await this.parent.getComponentWidth()\n\t\t\t},\n\t\t\tupdateParentData() {\n\t\t\t\tthis.getParentData('u-row')\n\t\t\t},\n\t\t\tclickHandler(e) {\n\t\t\t\tthis.$emit('click');\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-col {\n\t\tpadding: 0;\n\t\t/* #ifndef APP-NVUE */\n\t\tbox-sizing:border-box;\n\t\t/* #endif */\n\t\t/* #ifdef MP */\n\t\tdisplay: block;\n\t\t/* #endif */\n\t}\n\n\t// nvue下百分比无效\n\t/* #ifndef APP-NVUE */\n\t.u-col-0 {\n\t\twidth: 0;\n\t}\n\n\t.u-col-1 {\n\t\twidth: calc(100%/12);\n\t}\n\n\t.u-col-2 {\n\t\twidth: calc(100%/12 * 2);\n\t}\n\n\t.u-col-3 {\n\t\twidth: calc(100%/12 * 3);\n\t}\n\n\t.u-col-4 {\n\t\twidth: calc(100%/12 * 4);\n\t}\n\n\t.u-col-5 {\n\t\twidth: calc(100%/12 * 5);\n\t}\n\n\t.u-col-6 {\n\t\twidth: calc(100%/12 * 6);\n\t}\n\n\t.u-col-7 {\n\t\twidth: calc(100%/12 * 7);\n\t}\n\n\t.u-col-8 {\n\t\twidth: calc(100%/12 * 8);\n\t}\n\n\t.u-col-9 {\n\t\twidth: calc(100%/12 * 9);\n\t}\n\n\t.u-col-10 {\n\t\twidth: calc(100%/12 * 10);\n\t}\n\n\t.u-col-11 {\n\t\twidth: calc(100%/12 * 11);\n\t}\n\n\t.u-col-12 {\n\t\twidth: calc(100%/12 * 12);\n\t}\n\n\t/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-collapse/props.js",
    "content": "export default {\n    props: {\n        // 当前展开面板的name，非手风琴模式：[<string | number>]，手风琴模式：string | number\n        value: {\n            type: [String, Number, Array, null],\n            default: uni.$u.props.collapse.value\n        },\n        // 是否手风琴模式\n        accordion: {\n            type: Boolean,\n            default: uni.$u.props.collapse.accordion\n        },\n        // 是否显示外边框\n        border: {\n            type: Boolean,\n            default: uni.$u.props.collapse.border\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-collapse/u-collapse.vue",
    "content": "<template>\n\t<view class=\"u-collapse\">\n\t\t<u-line v-if=\"border\"></u-line>\n\t\t<slot />\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * collapse 折叠面板 \n\t * @description 通过折叠面板收纳内容区域\n\t * @tutorial https://www.uviewui.com/components/collapse.html\n\t * @property {String | Number | Array}\tvalue\t\t当前展开面板的name，非手风琴模式：[<string | number>]，手风琴模式：string | number\n\t * @property {Boolean}\t\t\t\t\taccordion\t是否手风琴模式（ 默认 false ）\n\t * @property {Boolean}\t\t\t\t\tborder\t\t是否显示外边框 ( 默认 true ）\n\t * @event {Function}\tchange \t\t当前激活面板展开时触发(如果是手风琴模式，参数activeNames类型为String，否则为Array)\n\t * @example <u-collapse></u-collapse>\n\t */\n\texport default {\n\t\tname: \"u-collapse\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\twatch: {\n\t\t\tneedInit() {\n\t\t\t\tthis.init()\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.children = []\n\t\t},\n\t\tcomputed: {\n\t\t\tneedInit() {\n\t\t\t\t// 通过computed，同时监听accordion和value值的变化\n\t\t\t\t// 再通过watch去执行init()方法，进行再一次的初始化\n\t\t\t\treturn [this.accordion, this.value]\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\n\t\t\tparentData() {\n\t\t\t\tif (this.children.length) {\n\t\t\t\t\tthis.children.map(child => {\n\t\t\t\t\t\t// 判断子组件(u-checkbox)如果有updateParentData方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\n\t\t\t\t\t\ttypeof(child.updateParentData) === 'function' && child.updateParentData()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tmethods: {\n\t\t\t// 重新初始化一次内部的所有子元素\n\t\t\tinit() {\n\t\t\t\tthis.children.map(child => {\n\t\t\t\t\tchild.init()\n\t\t\t\t})\n\t\t\t},\n\t\t\t/**\n\t\t\t * collapse-item被点击时触发，由collapse统一处理各子组件的状态\n\t\t\t * @param {Object} target 被操作的面板的实例\n\t\t\t */\n\t\t\tonChange(target) {\n\t\t\t\tlet changeArr = []\n\t\t\t\tthis.children.map((child, index) => {\n\t\t\t\t\t// 如果是手风琴模式，将其他的折叠面板收起来\n\t\t\t\t\tif (this.accordion) {\n\t\t\t\t\t\tchild.expanded = child === target ? !target.expanded : false\n\t\t\t\t\t\tchild.setContentAnimate()\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif(child === target) {\n\t\t\t\t\t\t\tchild.expanded = !child.expanded\n\t\t\t\t\t\t\tchild.setContentAnimate()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// 拼接change事件中，数组元素的状态\n\t\t\t\t\tchangeArr.push({\n\t\t\t\t\t\t// 如果没有定义name属性，则默认返回组件的index索引\n\t\t\t\t\t\tname: child.name || index,\n\t\t\t\t\t\tstatus: child.expanded ? 'open' : 'close'\n\t\t\t\t\t})\n\t\t\t\t})\n\n\t\t\t\tthis.$emit('change', changeArr)\n\t\t\t\tthis.$emit(target.expanded ? 'open' : 'close', target.name)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-collapse-item/props.js",
    "content": "export default {\n    props: {\n        // 标题\n        title: {\n            type: String,\n            default: uni.$u.props.collapseItem.title\n        },\n        // 标题右侧内容\n        value: {\n            type: String,\n            default: uni.$u.props.collapseItem.value\n        },\n        // 标题下方的描述信息\n        label: {\n            type: String,\n            default: uni.$u.props.collapseItem.label\n        },\n        // 是否禁用折叠面板\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.collapseItem.disabled\n        },\n        // 是否展示右侧箭头并开启点击反馈\n        isLink: {\n            type: Boolean,\n            default: uni.$u.props.collapseItem.isLink\n        },\n        // 是否开启点击反馈\n        clickable: {\n            type: Boolean,\n            default: uni.$u.props.collapseItem.clickable\n        },\n        // 是否显示内边框\n        border: {\n            type: Boolean,\n            default: uni.$u.props.collapseItem.border\n        },\n        // 标题的对齐方式\n        align: {\n            type: String,\n            default: uni.$u.props.collapseItem.align\n        },\n        // 唯一标识符\n        name: {\n            type: [String, Number],\n            default: uni.$u.props.collapseItem.name\n        },\n        // 标题左侧图片，可为绝对路径的图片或内置图标\n        icon: {\n            type: String,\n            default: uni.$u.props.collapseItem.icon\n        },\n        // 面板展开收起的过渡时间，单位ms\n        duration: {\n            type: Number,\n            default: uni.$u.props.collapseItem.duration\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-collapse-item/u-collapse-item.vue",
    "content": "<template>\n\t<view class=\"u-collapse-item\">\n\t\t<u-cell\n\t\t\t:title=\"title\"\n\t\t\t:value=\"value\"\n\t\t\t:label=\"label\"\n\t\t\t:icon=\"icon\"\n\t\t\t:isLink=\"isLink\"\n\t\t\t:clickable=\"clickable\"\n\t\t\t:border=\"parentData.border && showBorder\"\n\t\t\t@click=\"clickHandler\"\n\t\t\t:arrowDirection=\"expanded ? 'up' : 'down'\"\n\t\t\t:disabled=\"disabled\"\n\t\t>\n\t\t\t<!-- #ifndef MP-WEIXIN -->\n\t\t\t<!-- 微信小程序不支持，因为微信中不支持 <slot name=\"title\" slot=\"title\" />的写法 -->\n\t\t\t<template slot=\"title\">\n\t\t\t\t<slot name=\"title\"></slot>\n\t\t\t</template>\n\t\t\t<template slot=\"icon\">\n\t\t\t\t<slot name=\"icon\"></slot>\n\t\t\t</template>\n\t\t\t<template slot=\"value\">\n\t\t\t\t<slot name=\"value\"></slot>\n\t\t\t</template>\n\t\t\t<template slot=\"right-icon\">\n\t\t\t\t<slot name=\"right-icon\"></slot>\n\t\t\t</template>\n\t\t\t<!-- #endif -->\n\t\t</u-cell>\n\t\t<view\n\t\t\tclass=\"u-collapse-item__content\"\n\t\t\t:animation=\"animationData\"\n\t\t\tref=\"animation\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-collapse-item__content__text content-class\"\n\t\t\t\t:id=\"elId\"\n\t\t\t\t:ref=\"elId\"\n\t\t\t><slot /></view>\n\t\t</view>\n\t\t<u-line v-if=\"parentData.border\"></u-line>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst animation = uni.requireNativePlugin('animation')\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * collapseItem 折叠面板Item\n\t * @description 通过折叠面板收纳内容区域（搭配u-collapse使用）\n\t * @tutorial https://www.uviewui.com/components/collapse.html\n\t * @property {String}\t\t\ttitle \t\t标题\n\t * @property {String}\t\t\tvalue \t\t标题右侧内容\n\t * @property {String}\t\t\tlabel \t\t标题下方的描述信息\n\t * @property {Boolean}\t\t\tdisbled \t是否禁用折叠面板 ( 默认 false )\n\t * @property {Boolean}\t\t\tisLink \t\t是否展示右侧箭头并开启点击反馈 ( 默认 true )\n\t * @property {Boolean}\t\t\tclickable\t是否开启点击反馈 ( 默认 true )\n\t * @property {Boolean}\t\t\tborder\t\t是否显示内边框 ( 默认 true )\n\t * @property {String}\t\t\talign\t\t标题的对齐方式 ( 默认 'left' )\n\t * @property {String | Number}\tname\t\t唯一标识符\n\t * @property {String}\t\t\ticon\t\t标题左侧图片，可为绝对路径的图片或内置图标\n\t * @event {Function}\t\t\tchange \t\t\t某个item被打开或者收起时触发\n\t * @example <u-collapse-item :title=\"item.head\" v-for=\"(item, index) in itemList\" :key=\"index\">{{item.body}}</u-collapse-item>\n\t */\n\texport default {\n\t\tname: \"u-collapse-item\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\telId: uni.$u.guid(),\n\t\t\t\t// uni.createAnimation的导出数据\n\t\t\t\tanimationData: {},\n\t\t\t\t// 是否展开状态\n\t\t\t\texpanded: false,\n\t\t\t\t// 根据expanded确定是否显示border，为了控制展开时，cell的下划线更好的显示效果，进行一定时间的延时\n\t\t\t\tshowBorder: false,\n\t\t\t\t// 是否动画中，如果是则不允许继续触发点击\n\t\t\t\tanimating: false,\n\t\t\t\t// 父组件u-collapse的参数\n\t\t\t\tparentData: {\n\t\t\t\t\taccordion: false,\n\t\t\t\t\tborder: false\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\t\twatch: {\n\t\t\texpanded(n) {\n\t\t\t\tclearTimeout(this.timer)\n\t\t\t\tthis.timer = null\n\t\t\t\t// 这里根据expanded的值来进行一定的延时，是为了cell的下划线更好的显示效果\n\t\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\t\tthis.showBorder = n\n\t\t\t\t}, n ? 10 : 290)\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\t// 异步获取内容，或者动态修改了内容时，需要重新初始化\n\t\t\tinit() {\n\t\t\t\t// 初始化数据\n\t\t\t\tthis.updateParentData()\n\t\t\t\tif (!this.parent) {\n\t\t\t\t\treturn uni.$u.error('u-collapse-item必须要搭配u-collapse组件使用')\n\t\t\t\t}\n\t\t\t\tconst {\n\t\t\t\t\tvalue,\n\t\t\t\t\taccordion,\n\t\t\t\t\tchildren = []\n\t\t\t\t} = this.parent\n\n\t\t\t\tif (accordion) {\n\t\t\t\t\tif (uni.$u.test.array(value)) {\n\t\t\t\t\t\treturn uni.$u.error('手风琴模式下，u-collapse组件的value参数不能为数组')\n\t\t\t\t\t}\n\t\t\t\t\tthis.expanded = this.name == value\n\t\t\t\t} else {\n\t\t\t\t\tif (!uni.$u.test.array(value) && value !== null) {\n\t\t\t\t\t\treturn uni.$u.error('非手风琴模式下，u-collapse组件的value参数必须为数组')\n\t\t\t\t\t}\n\t\t\t\t\tthis.expanded = (value || []).some(item => item == this.name)\n\t\t\t\t}\n\t\t\t\t// 设置组件的展开或收起状态\n\t\t\t\tthis.$nextTick(function() {\n\t\t\t\t\tthis.setContentAnimate()\n\t\t\t\t})\n\t\t\t},\n\t\t\tupdateParentData() {\n\t\t\t\t// 此方法在mixin中\n\t\t\t\tthis.getParentData('u-collapse')\n\t\t\t},\n\t\t\tasync setContentAnimate() {\n\t\t\t\t// 每次面板打开或者收起时，都查询元素尺寸\n\t\t\t\t// 好处是，父组件从服务端获取内容后，变更折叠面板后可以获得最新的高度\n\t\t\t\tconst rect = await this.queryRect()\n\t\t\t\tconst height = this.expanded ? rect.height : 0\n\t\t\t\tthis.animating = true\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tconst ref = this.$refs['animation'].ref\n\t\t\t\tanimation.transition(ref, {\n\t\t\t\t\tstyles: {\n\t\t\t\t\t\theight: height + 'px'\n\t\t\t\t\t},\n\t\t\t\t\tduration: this.duration,\n\t\t\t\t\t// 必须设置为true，否则会到面板收起或展开时，页面其他元素不会随之调整它们的布局\n\t\t\t\t\tneedLayout: true,\n\t\t\t\t\ttimingFunction: 'ease-in-out',\n\t\t\t\t}, () => {\n\t\t\t\t\tthis.animating = false\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tconst animation = uni.createAnimation({\n\t\t\t\t\ttimingFunction: 'ease-in-out',\n\t\t\t\t});\n\t\t\t\tanimation\n\t\t\t\t\t.height(height)\n\t\t\t\t\t.step({\n\t\t\t\t\t\tduration: this.duration,\n\t\t\t\t\t})\n\t\t\t\t\t.step()\n\t\t\t\t// 导出动画数据给面板的animationData值\n\t\t\t\tthis.animationData = animation.export()\n\t\t\t\t// 标识动画结束\n\t\t\t\tuni.$u.sleep(this.duration).then(() => {\n\t\t\t\t\tthis.animating = false\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 点击collapsehead头部\n\t\t\tclickHandler() {\n\t\t\t\tif (this.disabled && this.animating) return\n\t\t\t\t// 设置本组件为相反的状态\n\t\t\t\tthis.parent && this.parent.onChange(this)\n\t\t\t},\n\t\t\t// 查询内容高度\n\t\t\tqueryRect() {\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t// $uGetRect为uView自带的节点查询简化方法，详见文档介绍：https://www.uviewui.com/js/getRect.html\n\t\t\t\t// 组件内部一般用this.$uGetRect，对外的为uni.$u.getRect，二者功能一致，名称不同\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tthis.$uGetRect(`#${this.elId}`).then(size => {\n\t\t\t\t\t\tresolve(size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// nvue下，使用dom模块查询元素高度\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tdom.getComponentRect(this.$refs[this.elId], res => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t}\n\t\t},\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-collapse-item {\n\n\t\t&__content {\n\t\t\toverflow: hidden;\n\t\t\theight: 0;\n\n\t\t\t&__text {\n\t\t\t\tpadding: 12px 15px;\n\t\t\t\tcolor: $u-content-color;\n\t\t\t\tfont-size: 14px;\n\t\t\t\tline-height: 18px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-column-notice/props.js",
    "content": "export default {\n    props: {\n        // 显示的内容，字符串\n        text: {\n            type: [Array],\n            default: uni.$u.props.columnNotice.text\n        },\n        // 是否显示左侧的音量图标\n        icon: {\n            type: String,\n            default: uni.$u.props.columnNotice.icon\n        },\n        // 通告模式，link-显示右箭头，closable-显示右侧关闭图标\n        mode: {\n            type: String,\n            default: uni.$u.props.columnNotice.mode\n        },\n        // 文字颜色，各图标也会使用文字颜色\n        color: {\n            type: String,\n            default: uni.$u.props.columnNotice.color\n        },\n        // 背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.columnNotice.bgColor\n        },\n        // 字体大小，单位px\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.columnNotice.fontSize\n        },\n        // 水平滚动时的滚动速度，即每秒滚动多少px(px)，这有利于控制文字无论多少时，都能有一个恒定的速度\n        speed: {\n            type: [String, Number],\n            default: uni.$u.props.columnNotice.speed\n        },\n        // direction = row时，是否使用步进形式滚动\n        step: {\n            type: Boolean,\n            default: uni.$u.props.columnNotice.step\n        },\n        // 滚动一个周期的时间长，单位ms\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.columnNotice.duration\n        },\n        // 是否禁止用手滑动切换\n        // 目前HX2.6.11，只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序\n        disableTouch: {\n            type: Boolean,\n            default: uni.$u.props.columnNotice.disableTouch\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-column-notice/u-column-notice.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-notice\"\n\t\t@tap=\"clickHandler\"\n\t>\n\t\t<slot name=\"icon\">\n\t\t\t<view\n\t\t\t\tclass=\"u-notice__left-icon\"\n\t\t\t\tv-if=\"icon\"\n\t\t\t>\n\t\t\t\t<u-icon\n\t\t\t\t\t:name=\"icon\"\n\t\t\t\t\t:color=\"color\"\n\t\t\t\t\tsize=\"19\"\n\t\t\t\t></u-icon>\n\t\t\t</view>\n\t\t</slot>\n\t\t<swiper\n\t\t\t:disable-touch=\"disableTouch\"\n\t\t\t:vertical=\"step ? false : true\"\n\t\t\tcircular\n\t\t\t:interval=\"duration\"\n\t\t\t:autoplay=\"true\"\n\t\t\tclass=\"u-notice__swiper\"\n\t\t\t@change=\"noticeChange\"\n\t\t>\n\t\t\t<swiper-item\n\t\t\t\tv-for=\"(item, index) in text\"\n\t\t\t\t:key=\"index\"\n\t\t\t\tclass=\"u-notice__swiper__item\"\n\t\t\t>\n\t\t\t\t<text\n\t\t\t\t\tclass=\"u-notice__swiper__item__text u-line-1\"\n\t\t\t\t\t:style=\"[textStyle]\"\n\t\t\t\t>{{ item }}</text>\n\t\t\t</swiper-item>\n\t\t</swiper>\n\t\t<view\n\t\t\tclass=\"u-notice__right-icon\"\n\t\t\tv-if=\"['link', 'closable'].includes(mode)\"\n\t\t>\n\t\t\t<u-icon\n\t\t\t\tv-if=\"mode === 'link'\"\n\t\t\t\tname=\"arrow-right\"\n\t\t\t\t:size=\"17\"\n\t\t\t\t:color=\"color\"\n\t\t\t></u-icon>\n\t\t\t<u-icon\n\t\t\t\tv-if=\"mode === 'closable'\"\n\t\t\t\tname=\"close\"\n\t\t\t\t:size=\"16\"\n\t\t\t\t:color=\"color\"\n\t\t\t\t@click=\"close\"\n\t\t\t></u-icon>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * ColumnNotice 滚动通知中的垂直滚动 内部组件\n\t * @description 该组件用于滚动通告场景，是其中的垂直滚动方式\n\t * @tutorial https://www.uviewui.com/components/noticeBar.html\n\t * @property {Array}\t\t\ttext \t\t\t显示的内容，字符串\n\t * @property {String}\t\t\ticon \t\t\t是否显示左侧的音量图标 （ 默认 'volume' ）\n\t * @property {String}\t\t\tmode \t\t\t通告模式，link-显示右箭头，closable-显示右侧关闭图标\n\t * @property {String}\t\t\tcolor \t\t\t文字颜色，各图标也会使用文字颜色 （ 默认 '#f9ae3d' ）\n\t * @property {String}\t\t\tbgColor \t\t背景颜色 （ 默认 '#fdf6ec' ）\n\t * @property {String | Number}\tfontSize\t\t字体大小，单位px  （ 默认 14 ）\n\t * @property {String | Number}\tspeed\t\t\t水平滚动时的滚动速度，即每秒滚动多少px(rpx)，这有利于控制文字无论多少时，都能有一个恒定的速度 （ 默认 80 ）\n\t * @property {Boolean}\t\t\tstep\t\t\tdirection = row时，是否使用步进形式滚动 （ 默认 false ）\n\t * @property {String | Number}\tduration\t\t滚动一个周期的时间长，单位ms （ 默认 1500 ）\n\t * @property {Boolean}\t\t\tdisableTouch\t是否禁止用手滑动切换   目前HX2.6.11，只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 （ 默认 true ）\n\t * @example \n\t */\n\texport default {\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\twatch: {\n\t\t\ttext: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newValue, oldValue) {\n\t\t\t\t\tif(!uni.$u.test.array(newValue)) {\n\t\t\t\t\t\tuni.$u.error('noticebar组件direction为column时，要求text参数为数组形式')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 文字内容的样式\n\t\t\ttextStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\tstyle.color = this.color\n\t\t\t\tstyle.fontSize = uni.$u.addUnit(this.fontSize)\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 垂直或者水平滚动\n\t\t\tvertical() {\n\t\t\t\tif (this.mode == 'horizontal') return false\n\t\t\t\telse return true\n\t\t\t},\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tindex:0\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tnoticeChange(e){\n\t\t\t\tthis.index = e.detail.current\n\t\t\t},\n\t\t\t// 点击通告栏\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('click', this.index)\n\t\t\t},\n\t\t\t// 点击关闭按钮\n\t\t\tclose() {\n\t\t\t\tthis.$emit('close')\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-notice {\n\t\t@include flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\n\t\t&__left-icon {\n\t\t\talign-items: center;\n\t\t\tmargin-right: 5px;\n\t\t}\n\n\t\t&__right-icon {\n\t\t\tmargin-left: 5px;\n\t\t\talign-items: center;\n\t\t}\n\n\t\t&__swiper {\n\t\t\theight: 16px;\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t\tflex: 1;\n\n\t\t\t&__item {\n\t\t\t\t@include flex;\n\t\t\t\talign-items: center;\n\t\t\t\toverflow: hidden;\n\n\t\t\t\t&__text {\n\t\t\t\t\tfont-size: 14px;\n\t\t\t\t\tcolor: $u-warning;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-count-down/props.js",
    "content": "export default {\n    props: {\n        // 倒计时时长，单位ms\n        time: {\n            type: [String, Number],\n            default: uni.$u.props.countDown.time\n        },\n        // 时间格式，DD-日，HH-时，mm-分，ss-秒，SSS-毫秒\n        format: {\n            type: String,\n            default: uni.$u.props.countDown.format\n        },\n        // 是否自动开始倒计时\n        autoStart: {\n            type: Boolean,\n            default: uni.$u.props.countDown.autoStart\n        },\n        // 是否展示毫秒倒计时\n        millisecond: {\n            type: Boolean,\n            default: uni.$u.props.countDown.millisecond\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-count-down/u-count-down.vue",
    "content": "<template>\n\t<view class=\"u-count-down\">\n\t\t<slot>\n\t\t\t<text class=\"u-count-down__text\">{{ formattedTime }}</text>\n\t\t</slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\timport {\n\t\tisSameSecond,\n\t\tparseFormat,\n\t\tparseTimeData\n\t} from './utils';\n\t/**\n\t * u-count-down 倒计时\n\t * @description 该组件一般使用于某个活动的截止时间上，通过数字的变化，给用户明确的时间感受，提示用户进行某一个行为操作。\n\t * @tutorial https://uviewui.com/components/countDown.html\n\t * @property {String | Number}\ttime\t\t倒计时时长，单位ms （默认 0 ）\n\t * @property {String}\t\t\tformat\t\t时间格式，DD-日，HH-时，mm-分，ss-秒，SSS-毫秒  （默认 'HH:mm:ss' ）\n\t * @property {Boolean}\t\t\tautoStart\t是否自动开始倒计时 （默认 true ）\n\t * @property {Boolean}\t\t\tmillisecond\t是否展示毫秒倒计时 （默认 false ）\n\t * @event {Function} finish 倒计时结束时触发 \n\t * @event {Function} change 倒计时变化时触发 \n\t * @event {Function} start\t开始倒计时\n\t * @event {Function} pause\t暂停倒计时 \n\t * @event {Function} reset\t重设倒计时，若 auto-start 为 true，重设后会自动开始倒计时 \n\t * @example <u-count-down :time=\"time\"></u-count-down>\n\t */\n\texport default {\n\t\tname: 'u-count-down',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\ttimer: null,\n\t\t\t\t// 各单位(天，时，分等)剩余时间\n\t\t\t\ttimeData: parseTimeData(0),\n\t\t\t\t// 格式化后的时间，如\"03:23:21\"\n\t\t\t\tformattedTime: '0',\n\t\t\t\t// 倒计时是否正在进行中\n\t\t\t\truning: false,\n\t\t\t\tendTime: 0, // 结束的毫秒时间戳\n\t\t\t\tremainTime: 0, // 剩余的毫秒时间\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\ttime(n) {\n\t\t\t\tthis.reset()\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tthis.reset()\n\t\t\t},\n\t\t\t// 开始倒计时\n\t\t\tstart() {\n\t\t\t\tif (this.runing) return\n\t\t\t\t// 标识为进行中\n\t\t\t\tthis.runing = true\n\t\t\t\t// 结束时间戳 = 此刻时间戳 + 剩余的时间\n\t\t\t\tthis.endTime = Date.now() + this.remainTime\n\t\t\t\tthis.toTick()\n\t\t\t},\n\t\t\t// 根据是否展示毫秒，执行不同操作函数\n\t\t\ttoTick() {\n\t\t\t\tif (this.millisecond) {\n\t\t\t\t\tthis.microTick()\n\t\t\t\t} else {\n\t\t\t\t\tthis.macroTick()\n\t\t\t\t}\n\t\t\t},\n\t\t\tmacroTick() {\n\t\t\t\tthis.clearTimeout()\n\t\t\t\t// 每隔一定时间，更新一遍定时器的值\n\t\t\t\t// 同时此定时器的作用也能带来毫秒级的更新\n\t\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\t\t// 获取剩余时间\n\t\t\t\t\tconst remain = this.getRemainTime()\n\t\t\t\t\t// 重设剩余时间\n\t\t\t\t\tif (!isSameSecond(remain, this.remainTime) || remain === 0) {\n\t\t\t\t\t\tthis.setRemainTime(remain)\n\t\t\t\t\t}\n\t\t\t\t\t// 如果剩余时间不为0，则继续检查更新倒计时\n\t\t\t\t\tif (this.remainTime !== 0) {\n\t\t\t\t\t\tthis.macroTick()\n\t\t\t\t\t}\n\t\t\t\t}, 30)\n\t\t\t},\n\t\t\tmicroTick() {\n\t\t\t\tthis.clearTimeout()\n\t\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\t\tthis.setRemainTime(this.getRemainTime())\n\t\t\t\t\tif (this.remainTime !== 0) {\n\t\t\t\t\t\tthis.microTick()\n\t\t\t\t\t}\n\t\t\t\t}, 50)\n\t\t\t},\n\t\t\t// 获取剩余的时间\n\t\t\tgetRemainTime() {\n\t\t\t\t// 取最大值，防止出现小于0的剩余时间值\n\t\t\t\treturn Math.max(this.endTime - Date.now(), 0)\n\t\t\t},\n\t\t\t// 设置剩余的时间\n\t\t\tsetRemainTime(remain) {\n\t\t\t\tthis.remainTime = remain\n\t\t\t\t// 根据剩余的毫秒时间，得出该有天，小时，分钟等的值，返回一个对象\n\t\t\t\tconst timeData = parseTimeData(remain)\n\t\t\t\tthis.$emit('change', timeData)\n\t\t\t\t// 得出格式化后的时间\n\t\t\t\tthis.formattedTime = parseFormat(this.format, timeData)\n\t\t\t\t// 如果时间已到，停止倒计时\n\t\t\t\tif (remain <= 0) {\n\t\t\t\t\tthis.pause()\n\t\t\t\t\tthis.$emit('finish')\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 重置倒计时\n\t\t\treset() {\n\t\t\t\tthis.pause()\n\t\t\t\tthis.remainTime = this.time\n\t\t\t\tthis.setRemainTime(this.remainTime)\n\t\t\t\tif (this.autoStart) {\n\t\t\t\t\tthis.start()\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 暂停倒计时\n\t\t\tpause() {\n\t\t\t\tthis.runing = false;\n\t\t\t\tthis.clearTimeout()\n\t\t\t},\n\t\t\t// 清空定时器\n\t\t\tclearTimeout() {\n\t\t\t\tclearTimeout(this.timer)\n\t\t\t\tthis.timer = null\n\t\t\t}\n\t\t},\n\t\tbeforeDestroy() {\n\t\t\tthis.clearTimeout()\n\t\t}\n\t}\n</script>\n\n<style\n\tlang=\"scss\"\n\tscoped\n>\n\t@import \"../../libs/css/components.scss\";\n\t$u-count-down-text-color:$u-content-color !default;\n\t$u-count-down-text-font-size:15px !default;\n\t$u-count-down-text-line-height:22px !default;\n\n\t.u-count-down {\n\t\t&__text {\n\t\t\tcolor: $u-count-down-text-color;\n\t\t\tfont-size: $u-count-down-text-font-size;\n\t\t\tline-height: $u-count-down-text-line-height;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-count-down/utils.js",
    "content": "// 补0，如1 -> 01\nfunction padZero(num, targetLength = 2) {\n    let str = `${num}`\n    while (str.length < targetLength) {\n        str = `0${str}`\n    }\n    return str\n}\nconst SECOND = 1000\nconst MINUTE = 60 * SECOND\nconst HOUR = 60 * MINUTE\nconst DAY = 24 * HOUR\nexport function parseTimeData(time) {\n    const days = Math.floor(time / DAY)\n    const hours = Math.floor((time % DAY) / HOUR)\n    const minutes = Math.floor((time % HOUR) / MINUTE)\n    const seconds = Math.floor((time % MINUTE) / SECOND)\n    const milliseconds = Math.floor(time % SECOND)\n    return {\n        days,\n        hours,\n        minutes,\n        seconds,\n        milliseconds\n    }\n}\nexport function parseFormat(format, timeData) {\n    let {\n        days,\n        hours,\n        minutes,\n        seconds,\n        milliseconds\n    } = timeData\n    // 如果格式化字符串中不存在DD(天)，则将天的时间转为小时中去\n    if (format.indexOf('DD') === -1) {\n        hours += days * 24\n    } else {\n        // 对天补0\n        format = format.replace('DD', padZero(days))\n    }\n    // 其他同理于DD的格式化处理方式\n    if (format.indexOf('HH') === -1) {\n        minutes += hours * 60\n    } else {\n        format = format.replace('HH', padZero(hours))\n    }\n    if (format.indexOf('mm') === -1) {\n        seconds += minutes * 60\n    } else {\n        format = format.replace('mm', padZero(minutes))\n    }\n    if (format.indexOf('ss') === -1) {\n        milliseconds += seconds * 1000\n    } else {\n        format = format.replace('ss', padZero(seconds))\n    }\n    return format.replace('SSS', padZero(milliseconds, 3))\n}\nexport function isSameSecond(time1, time2) {\n    return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-count-to/props.js",
    "content": "export default {\n    props: {\n        // 开始的数值，默认从0增长到某一个数\n        startVal: {\n            type: [String, Number],\n            default: uni.$u.props.countTo.startVal\n        },\n        // 要滚动的目标数值，必须\n        endVal: {\n            type: [String, Number],\n            default: uni.$u.props.countTo.endVal\n        },\n        // 滚动到目标数值的动画持续时间，单位为毫秒（ms）\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.countTo.duration\n        },\n        // 设置数值后是否自动开始滚动\n        autoplay: {\n            type: Boolean,\n            default: uni.$u.props.countTo.autoplay\n        },\n        // 要显示的小数位数\n        decimals: {\n            type: [String, Number],\n            default: uni.$u.props.countTo.decimals\n        },\n        // 是否在即将到达目标数值的时候，使用缓慢滚动的效果\n        useEasing: {\n            type: Boolean,\n            default: uni.$u.props.countTo.useEasing\n        },\n        // 十进制分割\n        decimal: {\n            type: [String, Number],\n            default: uni.$u.props.countTo.decimal\n        },\n        // 字体颜色\n        color: {\n            type: String,\n            default: uni.$u.props.countTo.color\n        },\n        // 字体大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.countTo.fontSize\n        },\n        // 是否加粗字体\n        bold: {\n            type: Boolean,\n            default: uni.$u.props.countTo.bold\n        },\n        // 千位分隔符，类似金额的分割(￥23,321.05中的\",\")\n        separator: {\n            type: String,\n            default: uni.$u.props.countTo.separator\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-count-to/u-count-to.vue",
    "content": "<template>\n\t<text\n\t\tclass=\"u-count-num\"\n\t\t:style=\"{\n\t\t\tfontSize: $u.addUnit(fontSize),\n\t\t\tfontWeight: bold ? 'bold' : 'normal',\n\t\t\tcolor: color\n\t\t}\"\n\t>{{ displayValue }}</text>\n</template>\n\n<script>\n\timport props from './props.js';\n/**\n * countTo 数字滚动\n * @description 该组件一般用于需要滚动数字到某一个值的场景，目标要求是一个递增的值。\n * @tutorial https://www.uviewui.com/components/countTo.html\n * @property {String | Number}\tstartVal\t开始的数值，默认从0增长到某一个数（默认 0 ）\n * @property {String | Number}\tendVal\t\t要滚动的目标数值，必须 （默认 0 ）\n * @property {String | Number}\tduration\t滚动到目标数值的动画持续时间，单位为毫秒（ms） （默认 2000 ）\n * @property {Boolean}\t\t\tautoplay\t设置数值后是否自动开始滚动 （默认 true ）\n * @property {String | Number}\tdecimals\t要显示的小数位数，见官网说明（默认 0 ）\n * @property {Boolean}\t\t\tuseEasing\t滚动结束时，是否缓动结尾，见官网说明（默认 true ）\n * @property {String}\t\t\tdecimal\t\t十进制分割 （ 默认 \".\" ）\n * @property {String}\t\t\tcolor\t\t字体颜色（ 默认 '#606266' )\n * @property {String | Number}\tfontSize\t字体大小，单位px（ 默认 22 ）\n * @property {Boolean}\t\t\tbold\t\t字体是否加粗（默认 false ）\n * @property {String}\t\t\tseparator\t千位分隔符，见官网说明\n * @event {Function} end 数值滚动到目标值时触发\n * @example <u-count-to ref=\"uCountTo\" :end-val=\"endVal\" :autoplay=\"autoplay\"></u-count-to>\n */\nexport default {\n\tname: 'u-count-to',\n\tdata() {\n\t\treturn {\n\t\t\tlocalStartVal: this.startVal,\n\t\t\tdisplayValue: this.formatNumber(this.startVal),\n\t\t\tprintVal: null,\n\t\t\tpaused: false, // 是否暂停\n\t\t\tlocalDuration: Number(this.duration),\n\t\t\tstartTime: null, // 开始的时间\n\t\t\ttimestamp: null, // 时间戳\n\t\t\tremaining: null, // 停留的时间\n\t\t\trAF: null,\n\t\t\tlastTime: 0 // 上一次的时间\n\t\t};\n\t},\n\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\tcomputed: {\n\t\tcountDown() {\n\t\t\treturn this.startVal > this.endVal;\n\t\t}\n\t},\n\twatch: {\n\t\tstartVal() {\n\t\t\tthis.autoplay && this.start();\n\t\t},\n\t\tendVal() {\n\t\t\tthis.autoplay && this.start();\n\t\t}\n\t},\n\tmounted() {\n\t\tthis.autoplay && this.start();\n\t},\n\tmethods: {\n\t\teasingFn(t, b, c, d) {\n\t\t\treturn (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;\n\t\t},\n\t\trequestAnimationFrame(callback) {\n\t\t\tconst currTime = new Date().getTime();\n\t\t\t// 为了使setTimteout的尽可能的接近每秒60帧的效果\n\t\t\tconst timeToCall = Math.max(0, 16 - (currTime - this.lastTime));\n\t\t\tconst id = setTimeout(() => {\n\t\t\t\tcallback(currTime + timeToCall);\n\t\t\t}, timeToCall);\n\t\t\tthis.lastTime = currTime + timeToCall;\n\t\t\treturn id;\n\t\t},\n\t\tcancelAnimationFrame(id) {\n\t\t\tclearTimeout(id);\n\t\t},\n\t\t// 开始滚动数字\n\t\tstart() {\n\t\t\tthis.localStartVal = this.startVal;\n\t\t\tthis.startTime = null;\n\t\t\tthis.localDuration = this.duration;\n\t\t\tthis.paused = false;\n\t\t\tthis.rAF = this.requestAnimationFrame(this.count);\n\t\t},\n\t\t// 暂定状态，重新再开始滚动；或者滚动状态下，暂停\n\t\treStart() {\n\t\t\tif (this.paused) {\n\t\t\t\tthis.resume();\n\t\t\t\tthis.paused = false;\n\t\t\t} else {\n\t\t\t\tthis.stop();\n\t\t\t\tthis.paused = true;\n\t\t\t}\n\t\t},\n\t\t// 暂停\n\t\tstop() {\n\t\t\tthis.cancelAnimationFrame(this.rAF);\n\t\t},\n\t\t// 重新开始(暂停的情况下)\n\t\tresume() {\n\t\t\tif (!this.remaining) return\n\t\t\tthis.startTime = 0;\n\t\t\tthis.localDuration = this.remaining;\n\t\t\tthis.localStartVal = this.printVal;\n\t\t\tthis.requestAnimationFrame(this.count);\n\t\t},\n\t\t// 重置\n\t\treset() {\n\t\t\tthis.startTime = null;\n\t\t\tthis.cancelAnimationFrame(this.rAF);\n\t\t\tthis.displayValue = this.formatNumber(this.startVal);\n\t\t},\n\t\tcount(timestamp) {\n\t\t\tif (!this.startTime) this.startTime = timestamp;\n\t\t\tthis.timestamp = timestamp;\n\t\t\tconst progress = timestamp - this.startTime;\n\t\t\tthis.remaining = this.localDuration - progress;\n\t\t\tif (this.useEasing) {\n\t\t\t\tif (this.countDown) {\n\t\t\t\t\tthis.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);\n\t\t\t\t} else {\n\t\t\t\t\tthis.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (this.countDown) {\n\t\t\t\t\tthis.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);\n\t\t\t\t} else {\n\t\t\t\t\tthis.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.countDown) {\n\t\t\t\tthis.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;\n\t\t\t} else {\n\t\t\t\tthis.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;\n\t\t\t}\n\t\t\tthis.displayValue = this.formatNumber(this.printVal) || 0;\n\t\t\tif (progress < this.localDuration) {\n\t\t\t\tthis.rAF = this.requestAnimationFrame(this.count);\n\t\t\t} else {\n\t\t\t\tthis.$emit('end');\n\t\t\t}\n\t\t},\n\t\t// 判断是否数字\n\t\tisNumber(val) {\n\t\t\treturn !isNaN(parseFloat(val));\n\t\t},\n\t\tformatNumber(num) {\n\t\t\t// 将num转为Number类型，因为其值可能为字符串数值，调用toFixed会报错\n\t\t\tnum = Number(num);\n\t\t\tnum = num.toFixed(Number(this.decimals));\n\t\t\tnum += '';\n\t\t\tconst x = num.split('.');\n\t\t\tlet x1 = x[0];\n\t\t\tconst x2 = x.length > 1 ? this.decimal + x[1] : '';\n\t\t\tconst rgx = /(\\d+)(\\d{3})/;\n\t\t\tif (this.separator && !this.isNumber(this.separator)) {\n\t\t\t\twhile (rgx.test(x1)) {\n\t\t\t\t\tx1 = x1.replace(rgx, '$1' + this.separator + '$2');\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn x1 + x2;\n\t\t},\n\t\tdestroyed() {\n\t\t\tthis.cancelAnimationFrame(this.rAF);\n\t\t}\n\t}\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n\n.u-count-num {\n\t/* #ifndef APP-NVUE */\n\tdisplay: inline-flex;\n\t/* #endif */\n\ttext-align: center;\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-datetime-picker/props.js",
    "content": "export default {\n    props: {\n        // 是否打开组件\n        show: {\n            type: Boolean,\n            default: uni.$u.props.datetimePicker.show\n        },\n        // 是否展示顶部的操作栏\n        showToolbar: {\n            type: Boolean,\n            default: uni.$u.props.datetimePicker.showToolbar\n        },\n        // 绑定值\n        value: {\n            type: [String, Number],\n            default: uni.$u.props.datetimePicker.value\n        },\n        // 顶部标题\n        title: {\n            type: String,\n            default: uni.$u.props.datetimePicker.title\n        },\n        // 展示格式，mode=date为日期选择，mode=time为时间选择，mode=year-month为年月选择，mode=datetime为日期时间选择\n        mode: {\n            type: String,\n            default: uni.$u.props.datetimePicker.mode\n        },\n        // 可选的最大时间\n        maxDate: {\n            type: Number,\n            // 最大默认值为后10年\n            default: uni.$u.props.datetimePicker.maxDate\n        },\n        // 可选的最小时间\n        minDate: {\n            type: Number,\n            // 最小默认值为前10年\n            default: uni.$u.props.datetimePicker.minDate\n        },\n        // 可选的最小小时，仅mode=time有效\n        minHour: {\n            type: Number,\n            default: uni.$u.props.datetimePicker.minHour\n        },\n        // 可选的最大小时，仅mode=time有效\n        maxHour: {\n            type: Number,\n            default: uni.$u.props.datetimePicker.maxHour\n        },\n        // 可选的最小分钟，仅mode=time有效\n        minMinute: {\n            type: Number,\n            default: uni.$u.props.datetimePicker.minMinute\n        },\n        // 可选的最大分钟，仅mode=time有效\n        maxMinute: {\n            type: Number,\n            default: uni.$u.props.datetimePicker.maxMinute\n        },\n        // 选项过滤函数\n        filter: {\n            type: [Function, null],\n            default: uni.$u.props.datetimePicker.filter\n        },\n        // 选项格式化函数\n        formatter: {\n            type: [Function, null],\n            default: uni.$u.props.datetimePicker.formatter\n        },\n        // 是否显示加载中状态\n        loading: {\n            type: Boolean,\n            default: uni.$u.props.datetimePicker.loading\n        },\n        // 各列中，单个选项的高度\n        itemHeight: {\n            type: [String, Number],\n            default: uni.$u.props.datetimePicker.itemHeight\n        },\n        // 取消按钮的文字\n        cancelText: {\n            type: String,\n            default: uni.$u.props.datetimePicker.cancelText\n        },\n        // 确认按钮的文字\n        confirmText: {\n            type: String,\n            default: uni.$u.props.datetimePicker.confirmText\n        },\n        // 取消按钮的颜色\n        cancelColor: {\n            type: String,\n            default: uni.$u.props.datetimePicker.cancelColor\n        },\n        // 确认按钮的颜色\n        confirmColor: {\n            type: String,\n            default: uni.$u.props.datetimePicker.confirmColor\n        },\n        // 每列中可见选项的数量\n        visibleItemCount: {\n            type: [String, Number],\n            default: uni.$u.props.datetimePicker.visibleItemCount\n        },\n        // 是否允许点击遮罩关闭选择器\n        closeOnClickOverlay: {\n            type: Boolean,\n            default: uni.$u.props.datetimePicker.closeOnClickOverlay\n        },\n        // 各列的默认索引\n        defaultIndex: {\n            type: Array,\n            default: uni.$u.props.datetimePicker.defaultIndex\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-datetime-picker/u-datetime-picker.vue",
    "content": "<template>\n\t<u-picker\n\t\tref=\"picker\"\n\t\t:show=\"show\"\n\t\t:closeOnClickOverlay=\"closeOnClickOverlay\"\n\t\t:columns=\"columns\"\n\t\t:title=\"title\"\n\t\t:itemHeight=\"itemHeight\"\n\t\t:showToolbar=\"showToolbar\"\n\t\t:visibleItemCount=\"visibleItemCount\"\n\t\t:defaultIndex=\"innerDefaultIndex\"\n\t\t:cancelText=\"cancelText\"\n\t\t:confirmText=\"confirmText\"\n\t\t:cancelColor=\"cancelColor\"\n\t\t:confirmColor=\"confirmColor\"\n\t\t@close=\"close\"\n\t\t@cancel=\"cancel\"\n\t\t@confirm=\"confirm\"\n\t\t@change=\"change\"\n\t>\n\t</u-picker>\n</template>\n\n<script>\n\tfunction times(n, iteratee) {\n\t    let index = -1\n\t    const result = Array(n < 0 ? 0 : n)\n\t    while (++index < n) {\n\t        result[index] = iteratee(index)\n\t    }\n\t    return result\n\t}\n\timport props from './props.js';\n\timport dayjs from '../../libs/util/dayjs.js';\n\t/**\n\t * DatetimePicker 时间日期选择器\n\t * @description 此选择器用于时间日期\n\t * @tutorial https://www.uviewui.com/components/datetimePicker.html\n\t * @property {Boolean}\t\t\tshow\t\t\t\t用于控制选择器的弹出与收起 ( 默认 false )\n\t * @property {Boolean}\t\t\tshowToolbar\t\t\t是否显示顶部的操作栏  ( 默认 true )\n\t * @property {String | Number}\tvalue\t\t\t\t绑定值\n\t * @property {String}\t\t\ttitle\t\t\t\t顶部标题\n\t * @property {String}\t\t\tmode\t\t\t\t展示格式 mode=date为日期选择，mode=time为时间选择，mode=year-month为年月选择，mode=datetime为日期时间选择  ( 默认 ‘datetime )\n\t * @property {Number}\t\t\tmaxDate\t\t\t\t可选的最大时间  默认值为后10年\n\t * @property {Number}\t\t\tminDate\t\t\t\t可选的最小时间  默认值为前10年\n\t * @property {Number}\t\t\tminHour\t\t\t\t可选的最小小时，仅mode=time有效   ( 默认 0 )\n\t * @property {Number}\t\t\tmaxHour\t\t\t\t可选的最大小时，仅mode=time有效\t  ( 默认 23 )\n\t * @property {Number}\t\t\tminMinute\t\t\t可选的最小分钟，仅mode=time有效\t  ( 默认 0 )\n\t * @property {Number}\t\t\tmaxMinute\t\t\t可选的最大分钟，仅mode=time有效   ( 默认 59 )\n\t * @property {Function}\t\t\tfilter\t\t\t\t选项过滤函数\n\t * @property {Function}\t\t\tformatter\t\t\t选项格式化函数\n\t * @property {Boolean}\t\t\tloading\t\t\t\t是否显示加载中状态   ( 默认 false )\n\t * @property {String | Number}\titemHeight\t\t\t各列中，单个选项的高度   ( 默认 44 )\n\t * @property {String}\t\t\tcancelText\t\t\t取消按钮的文字  ( 默认 '取消' )\n\t * @property {String}\t\t\tconfirmText\t\t\t确认按钮的文字  ( 默认 '确认' )\n\t * @property {String}\t\t\tcancelColor\t\t\t取消按钮的颜色  ( 默认 '#909193' )\n\t * @property {String}\t\t\tconfirmColor\t\t确认按钮的颜色  ( 默认 '#3c9cff' )\n\t * @property {String | Number}\tvisibleItemCount\t每列中可见选项的数量  ( 默认 5 )\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t是否允许点击遮罩关闭选择器  ( 默认 false )\n\t * @property {Array}\t\t\tdefaultIndex\t\t各列的默认索引\n\t * @event {Function} close 关闭选择器时触发\n\t * @event {Function} confirm 点击确定按钮，返回当前选择的值\n\t * @event {Function} change 当选择值变化时触发\n\t * @event {Function} cancel 点击取消按钮\n\t * @example  <u-datetime-picker :show=\"show\" :value=\"value1\"  mode=\"datetime\" ></u-datetime-picker>\n\t */\n\texport default {\n\t\tname: 'datetime-picker',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tcolumns: [],\n\t\t\t\tinnerDefaultIndex: [],\n\t\t\t\tinnerFormatter: (type, value) => value\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tshow(newValue, oldValue) {\n\t\t\t\tif (newValue) {\n\t\t\t\t\tthis.updateColumnValue(this.innerValue)\n\t\t\t\t}\n\t\t\t},\n\t\t\tpropsChange() {\n\t\t\t\tthis.init()\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 如果以下这些变量发生了变化，意味着需要重新初始化各列的值\n\t\t\tpropsChange() {\n\t\t\t\treturn [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, ]\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tthis.innerValue = this.correctValue(this.value)\n\t\t\t\tthis.updateColumnValue(this.innerValue)\n\t\t\t},\n\t\t\t// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用\n\t\t\tsetFormatter(e) {\n\t\t\t\tthis.innerFormatter = e\n\t\t\t},\n\t\t\t// 关闭选择器\n\t\t\tclose() {\n\t\t\t\tif (this.closeOnClickOverlay) {\n\t\t\t\t\tthis.$emit('close')\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 点击工具栏的取消按钮\n\t\t\tcancel() {\n\t\t\t\tthis.$emit('cancel')\n\t\t\t},\n\t\t\t// 点击工具栏的确定按钮\n\t\t\tconfirm() {\n\t\t\t\tthis.$emit('confirm', {\n\t\t\t\t\tvalue: this.innerValue,\n\t\t\t\t\tmode: this.mode\n\t\t\t\t})\n\t\t\t\tthis.$emit('input', this.innerValue)\n\t\t\t},\n\t\t\t//用正则截取输出值,当出现多组数字时,抛出错误\n\t\t\tintercept(e,type){\n\t\t\t\tlet judge = e.match(/\\d+/g)\n\t\t\t\t//判断是否掺杂数字\n\t\t\t\tif(judge.length>1){\n\t\t\t\t\tuni.$u.error(\"请勿在过滤或格式化函数时添加数字\")\n\t\t\t\t\treturn 0\n\t\t\t\t}else if(type&&judge[0].length==4){//判断是否是年份\n\t\t\t\t\treturn judge[0]\n\t\t\t\t}else if(judge[0].length>2){\n\t\t\t\t\tuni.$u.error(\"请勿在过滤或格式化函数时添加数字\")\n\t\t\t\t\treturn 0\n\t\t\t\t}else{\n\t\t\t\t\treturn judge[0]\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 列发生变化时触发\n\t\t\tchange(e) {\n\t\t\t\tconst { indexs, values } = e\n\t\t\t\tlet selectValue = ''\n\t\t\t\tif(this.mode === 'time') {\n\t\t\t\t\t// 根据value各列索引，从各列数组中，取出当前时间的选中值\n\t\t\t\t\tselectValue = `${this.intercept(values[0][indexs[0]])}:${this.intercept(values[1][indexs[1]])}`\n\t\t\t\t} else {\n\t\t\t\t\t// 将选择的值转为数值，比如'03'转为数值的3，'2019'转为数值的2019\n\t\t\t\t\tconst year = parseInt(this.intercept(values[0][indexs[0]],'year'))\n\t\t\t\t\tconst month = parseInt(this.intercept(values[1][indexs[1]]))\n\t\t\t\t\tlet date = parseInt(values[2] ? this.intercept(values[2][indexs[2]]) : 1)\n\t\t\t\t\tlet hour = 0, minute = 0\n\t\t\t\t\t// 此月份的最大天数\n\t\t\t\t\tconst maxDate = dayjs(`${year}-${month}`).daysInMonth()\n\t\t\t\t\t// year-month模式下，date不会出现在列中，设置为1，为了符合后边需要减1的需求\n\t\t\t\t\tif (this.mode === 'year-month') {\n\t\t\t\t\t    date = 1\n\t\t\t\t\t}\n\t\t\t\t\t// 不允许超过maxDate值\n\t\t\t\t\tdate = Math.min(maxDate, date)\n\t\t\t\t\tif (this.mode === 'datetime') {\n\t\t\t\t\t    hour = parseInt(this.intercept(values[3][indexs[3]]))\n\t\t\t\t\t    minute = parseInt(this.intercept(values[4][indexs[4]]))\n\t\t\t\t\t}\n\t\t\t\t\t// 转为时间模式\n\t\t\t\t\tselectValue = Number(new Date(year, month - 1, date, hour, minute))\n\t\t\t\t}\n\t\t\t\t// 取出准确的合法值，防止超越边界的情况\n\t\t\t\tselectValue = this.correctValue(selectValue)\n\t\t\t\tthis.innerValue = selectValue\n\t\t\t\tthis.updateColumnValue(selectValue)\n\t\t\t\t// 发出change时间，value为当前选中的时间戳\n\t\t\t\tthis.$emit('change', {\n\t\t\t\t\tvalue: selectValue,\n\t\t\t\t\t// #ifndef MP-WEIXIN\n\t\t\t\t\t// 微信小程序不能传递this实例，会因为循环引用而报错\n\t\t\t\t\tpicker: this.$refs.picker,\n\t\t\t\t\t// #endif\n\t\t\t\t\tmode: this.mode\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 更新各列的值，进行补0、格式化等操作\n\t\t\tupdateColumnValue(value) {\n\t\t\t\tthis.innerValue = value\n\t\t\t\tthis.updateColumns()\n\t\t\t\tthis.updateIndexs(value)\n\t\t\t},\n\t\t\t// 更新索引\n\t\t\tupdateIndexs(value) {\n\t\t\t\tlet values = []\n\t\t\t\tconst formatter = this.formatter || this.innerFormatter\n\t\t\t\tconst padZero = uni.$u.padZero\n\t\t\t\tif (this.mode === 'time') {\n\t\t\t\t\t// 将time模式的时间用:分隔成数组\n\t\t\t\t    const timeArr = value.split(':')\n\t\t\t\t\t// 使用formatter格式化方法进行管道处理\n\t\t\t\t    values = [formatter('hour', timeArr[0]), formatter('minute', timeArr[1])]\n\t\t\t\t} else {\n\t\t\t\t    const date = new Date(value)\n\t\t\t\t    values = [\n\t\t\t\t        formatter('year', `${dayjs(value).year()}`),\n\t\t\t\t\t\t// 月份补0\n\t\t\t\t        formatter('month', padZero(dayjs(value).month() + 1))\n\t\t\t\t    ]\n\t\t\t\t    if (this.mode === 'date') {\n\t\t\t\t\t\t// date模式，需要添加天列\n\t\t\t\t        values.push(formatter('day', padZero(dayjs(value).date())))\n\t\t\t\t    }\n\t\t\t\t    if (this.mode === 'datetime') {\n\t\t\t\t\t\t// 数组的push方法，可以写入多个参数\n\t\t\t\t        values.push(formatter('day', padZero(dayjs(value).date())), formatter('hour', padZero(dayjs(value).hour())), formatter('minute', padZero(dayjs(value).minute())))\n\t\t\t\t    }\n\t\t\t\t}\n\n\t\t\t\t// 根据当前各列的所有值，从各列默认值中找到默认值在各列中的索引\n\t\t\t\tconst indexs = this.columns.map((column, index) => {\n\t\t\t\t\t// 通过取大值，可以保证不会出现找不到索引的-1情况\n\t\t\t\t\treturn Math.max(0, column.findIndex(item => item === values[index]))\n\t\t\t\t})\n\t\t\t\tconsole.log(this.innerDefaultIndex);\n\t\t\t\tthis.innerDefaultIndex = indexs\n\t\t\t},\n\t\t\t// 更新各列的值\n\t\t\tupdateColumns() {\n\t\t\t    const formatter = this.formatter || this.innerFormatter\n\t\t\t\t// 获取各列的值，并且map后，对各列的具体值进行补0操作\n\t\t\t    const results = this.getOriginColumns().map((column) => column.values.map((value) => formatter(column.type, value)))\n\t\t\t\tthis.columns = results\n\t\t\t},\n\t\t\tgetOriginColumns() {\n\t\t\t    // 生成各列的值\n\t\t\t    const results = this.getRanges().map(({ type, range }) => {\n\t\t\t        let values = times(range[1] - range[0] + 1, (index) => {\n\t\t\t            let value = range[0] + index\n\t\t\t            value = type === 'year' ? `${value}` : uni.$u.padZero(value)\n\t\t\t            return value\n\t\t\t        })\n\t\t\t\t\t// 进行过滤\n\t\t\t        if (this.filter) {\n\t\t\t            values = this.filter(type, values)\n\t\t\t        }\n\t\t\t        return { type, values }\n\t\t\t    })\n\t\t\t    return results\n\t\t\t},\n\t\t\t// 通过最大值和最小值生成数组\n\t\t\tgenerateArray(start, end) {\n\t\t\t\treturn Array.from(new Array(end + 1).keys()).slice(start)\n\t\t\t},\n\t\t\t// 得出合法的时间\n\t\t\tcorrectValue(value) {\n\t\t\t\tconst isDateMode = this.mode !== 'time'\n\t\t\t\tif (isDateMode && !uni.$u.test.date(value)) {\n\t\t\t\t\t// 如果是日期类型，但是又没有设置合法的当前时间的话，使用最小时间为当前时间\n\t\t\t\t\tvalue = this.minDate\n\t\t\t\t} else if (!isDateMode && !value) {\n\t\t\t\t\t// 如果是时间类型，而又没有默认值的话，就用最小时间\n\t\t\t\t\tvalue = `${uni.$u.padZero(this.minHour)}:${uni.$u.padZero(this.minMinute)}`\n\t\t\t\t}\n\t\t\t\t// 时间类型\n\t\t\t\tif (!isDateMode) {\n\t\t\t\t\tif (String(value).indexOf(':') === -1) return uni.$u.error('时间错误，请传递如12:24的格式')\n\t\t\t\t\tlet [hour, minute] = value.split(':')\n\t\t\t\t\t// 对时间补零，同时控制在最小值和最大值之间\n\t\t\t\t\thour = uni.$u.padZero(uni.$u.range(this.minHour, this.maxHour, Number(hour)))\n\t\t\t\t\tminute = uni.$u.padZero(uni.$u.range(this.minMinute, this.maxMinute, Number(minute)))\n\t\t\t\t\treturn `${ hour }:${ minute }`\n\t\t\t\t} else {\n\t\t\t\t\t// 如果是日期格式，控制在最小日期和最大日期之间\n\t\t\t\t\tvalue = dayjs(value).isBefore(dayjs(this.minDate)) ? this.minDate : value\n\t\t\t\t\tvalue = dayjs(value).isAfter(dayjs(this.maxDate)) ? this.maxDate : value\n\t\t\t\t\treturn value\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 获取每列的最大和最小值\n\t\t\tgetRanges() {\n\t\t\t    if (this.mode === 'time') {\n\t\t\t        return [\n\t\t\t            {\n\t\t\t                type: 'hour',\n\t\t\t                range: [this.minHour, this.maxHour],\n\t\t\t            },\n\t\t\t            {\n\t\t\t                type: 'minute',\n\t\t\t                range: [this.minMinute, this.maxMinute],\n\t\t\t            },\n\t\t\t        ];\n\t\t\t    }\n\t\t\t    const { maxYear, maxDate, maxMonth, maxHour, maxMinute, } = this.getBoundary('max', this.innerValue);\n\t\t\t    const { minYear, minDate, minMonth, minHour, minMinute, } = this.getBoundary('min', this.innerValue);\n\t\t\t    const result = [\n\t\t\t        {\n\t\t\t            type: 'year',\n\t\t\t            range: [minYear, maxYear],\n\t\t\t        },\n\t\t\t        {\n\t\t\t            type: 'month',\n\t\t\t            range: [minMonth, maxMonth],\n\t\t\t        },\n\t\t\t        {\n\t\t\t            type: 'day',\n\t\t\t            range: [minDate, maxDate],\n\t\t\t        },\n\t\t\t        {\n\t\t\t            type: 'hour',\n\t\t\t            range: [minHour, maxHour],\n\t\t\t        },\n\t\t\t        {\n\t\t\t            type: 'minute',\n\t\t\t            range: [minMinute, maxMinute],\n\t\t\t        },\n\t\t\t    ];\n\t\t\t    if (this.mode === 'date')\n\t\t\t        result.splice(3, 2);\n\t\t\t    if (this.mode === 'year-month')\n\t\t\t        result.splice(2, 3);\n\t\t\t    return result;\n\t\t\t},\n\t\t\t// 根据minDate、maxDate、minHour、maxHour等边界值，判断各列的开始和结束边界值\n\t\t\tgetBoundary(type, innerValue) {\n\t\t\t    const value = new Date(innerValue)\n\t\t\t    const boundary = new Date(this[`${type}Date`])\n\t\t\t    const year = dayjs(boundary).year()\n\t\t\t    let month = 1\n\t\t\t    let date = 1\n\t\t\t    let hour = 0\n\t\t\t    let minute = 0\n\t\t\t    if (type === 'max') {\n\t\t\t        month = 12\n\t\t\t\t\t// 月份的天数\n\t\t\t        date = dayjs(value).daysInMonth()\n\t\t\t        hour = 23\n\t\t\t        minute = 59\n\t\t\t    }\n\t\t\t\t// 获取边界值，逻辑是：当年达到了边界值(最大或最小年)，就检查月允许的最大和最小值，以此类推\n\t\t\t    if (dayjs(value).year() === year) {\n\t\t\t        month = dayjs(boundary).month() + 1\n\t\t\t        if (dayjs(value).month() + 1 === month) {\n\t\t\t            date = dayjs(boundary).date()\n\t\t\t            if (dayjs(value).date() === date) {\n\t\t\t                hour = dayjs(boundary).hour()\n\t\t\t                if (dayjs(value).hour() === hour) {\n\t\t\t                    minute = dayjs(boundary).minute()\n\t\t\t                }\n\t\t\t            }\n\t\t\t        }\n\t\t\t    }\n\t\t\t    return {\n\t\t\t        [`${type}Year`]: year,\n\t\t\t        [`${type}Month`]: month,\n\t\t\t        [`${type}Date`]: date,\n\t\t\t        [`${type}Hour`]: hour,\n\t\t\t        [`${type}Minute`]: minute\n\t\t\t    }\n\t\t\t},\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import '../../libs/css/components.scss';\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-divider/props.js",
    "content": "export default {\n    props: {\n        // 是否虚线\n        dashed: {\n            type: Boolean,\n            default: uni.$u.props.divider.dashed\n        },\n        // 是否细线\n        hairline: {\n            type: Boolean,\n            default: uni.$u.props.divider.hairline\n        },\n        // 是否以点替代文字，优先于text字段起作用\n        dot: {\n            type: Boolean,\n            default: uni.$u.props.divider.dot\n        },\n        // 内容文本的位置，left-左边，center-中间，right-右边\n        textPosition: {\n            type: String,\n            default: uni.$u.props.divider.textPosition\n        },\n        // 文本内容\n        text: {\n            type: [String, Number],\n            default: uni.$u.props.divider.text\n        },\n        // 文本大小\n        textSize: {\n            type: [String, Number],\n            default: uni.$u.props.divider.textSize\n        },\n        // 文本颜色\n        textColor: {\n            type: String,\n            default: uni.$u.props.divider.textColor\n        },\n        // 线条颜色\n        lineColor: {\n            type: String,\n            default: uni.$u.props.divider.lineColor\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-divider/u-divider.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-divider\"\n\t    :style=\"[$u.addStyle(customStyle)]\"\n\t\t@tap=\"click\"\n\t>\n\t\t<u-line\n\t\t    :color=\"lineColor\"\n\t\t    :customStyle=\"leftLineStyle\"\n\t\t    :hairline=\"hairline\"\n\t\t\t:dashed=\"dashed\"\n\t\t></u-line>\n\t\t<text\n\t\t    v-if=\"dot\"\n\t\t    class=\"u-divider__dot\"\n\t\t>●</text>\n\t\t<text\n\t\t    v-else-if=\"text\"\n\t\t    class=\"u-divider__text\"\n\t\t    :style=\"[textStyle]\"\n\t\t>{{text}}</text>\n\t\t<u-line\n\t\t    :color=\"lineColor\"\n\t\t    :customStyle=\"rightLineStyle\"\n\t\t    :hairline=\"hairline\"\n\t\t\t:dashed=\"dashed\"\n\t\t></u-line>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * divider 分割线\n\t * @description 区隔内容的分割线，一般用于页面底部\"没有更多\"的提示。\n\t * @tutorial https://www.uviewui.com/components/divider.html\n\t * @property {Boolean}\t\t\tdashed\t\t\t是否虚线 （默认 false ）\n\t * @property {Boolean}\t\t\thairline\t\t是否细线 （默认  true ）\n\t * @property {Boolean}\t\t\tdot\t\t\t\t是否以点替代文字，优先于text字段起作用 （默认 false ）\n\t * @property {String}\t\t\ttextPosition\t内容文本的位置，left-左边，center-中间，right-右边 （默认 'center' ）\n\t * @property {String | Number}\ttext\t\t\t文本内容\n\t * @property {String | Number}\ttextSize\t\t文本大小 （默认 14）\n\t * @property {String}\t\t\ttextColor\t\t文本颜色 （默认 '#909399' ）\n\t * @property {String}\t\t\tlineColor\t\t线条颜色 （默认 '#dcdfe6' ）\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\n\t *\n\t * @event {Function}\tclick\tdivider组件被点击时触发\n\t * @example <u-divider :color=\"color\">锦瑟无端五十弦</u-divider>\n\t */\n\texport default {\n\t\tname:'u-divider',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\ttextStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.fontSize = uni.$u.addUnit(this.textSize)\n\t\t\t\tstyle.color = this.textColor\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 左边线条的的样式\n\t\t\tleftLineStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\t// 如果是在左边，设置左边的宽度为固定值\n\t\t\t\tif (this.textPosition === 'left') {\n\t\t\t\t\tstyle.width = '80rpx'\n\t\t\t\t} else {\n\t\t\t\t\tstyle.flex = 1\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 右边线条的的样式\n\t\t\trightLineStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\t// 如果是在右边，设置右边的宽度为固定值\n\t\t\t\tif (this.textPosition === 'right') {\n\t\t\t\t\tstyle.width = '80rpx'\n\t\t\t\t} else {\n\t\t\t\t\tstyle.flex = 1\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// divider组件被点击时触发\n\t\t\tclick() {\n\t\t\t\tthis.$emit('click');\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import '../../libs/css/components.scss';\n\t$u-divider-margin:15px 0 !default;\n\t$u-divider-text-margin:0 15px !default;\n\t$u-divider-dot-font-size:12px !default;\n\t$u-divider-dot-margin:0 12px !default;\n\t$u-divider-dot-color: #c0c4cc !default;\n\n\t.u-divider {\n\t\t@include flex;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tmargin: $u-divider-margin;\n\n\t\t&__text {\n\t\t\tmargin: $u-divider-text-margin;\n\t\t}\n\n\t\t&__dot {\n\t\t\tfont-size: $u-divider-dot-font-size;\n\t\t\tmargin: $u-divider-dot-margin;\n\t\t\tcolor: $u-divider-dot-color;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-dropdown/props.js",
    "content": "export default {\n    props: {\n        // 标题选中时的样式\n        activeStyle: {\n            type: [String, Object],\n            default: () => ({\n                color: '#2979ff',\n                fontSize: '14px'\n            })\n        },\n        // 标题未选中时的样式\n        inactiveStyle: {\n            type: [String, Object],\n            default: () => ({\n                color: '#606266',\n                fontSize: '14px'\n            })\n        },\n        // 点击遮罩是否关闭菜单\n        closeOnClickMask: {\n            type: Boolean,\n            default: true\n        },\n        // 点击当前激活项标题是否关闭菜单\n        closeOnClickSelf: {\n            type: Boolean,\n            default: true\n        },\n        // 过渡时间\n        duration: {\n            type: [Number, String],\n            default: 300\n        },\n        // 标题菜单的高度\n        height: {\n            type: [Number, String],\n            default: 40\n        },\n        // 是否显示下边框\n        borderBottom: {\n            type: Boolean,\n            default: false\n        },\n        // 标题的字体大小\n        titleSize: {\n            type: [Number, String],\n            default: 14\n        },\n        // 下拉出来的内容部分的圆角值\n        borderRadius: {\n            type: [Number, String],\n            default: 0\n        },\n        // 菜单右侧的icon图标\n        menuIcon: {\n            type: String,\n            default: 'arrow-down'\n        },\n        // 菜单右侧图标的大小\n        menuIconSize: {\n            type: [Number, String],\n            default: 14\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue",
    "content": "<template>\n  <view class=\"u-drawdown\">\n    <view\n      class=\"u-dropdown__menu\"\n      :style=\"{\n\t\t\t\theight: $u.addUnit(height)\n\t\t\t}\"\n      ref=\"u-dropdown__menu\"\n    >\n      <view\n        class=\"u-dropdown__menu__item\"\n        v-for=\"(item, index) in menuList\"\n        :key=\"index\"\n        @tap.stop=\"clickHandler(item, index)\"\n      >\n        <view class=\"u-dropdown__menu__item__content\">\n          <text\n            class=\"u-dropdown__menu__item__content__text\"\n            :style=\"[index === current ? activeStyle : inactiveStyle]\"\n          >{{item.title}}</text>\n          <view\n            class=\"u-dropdown__menu__item__content__arrow\"\n            :class=\"[index === current && 'u-dropdown__menu__item__content__arrow--rotate']\"\n          >\n            <u-icon\n              :name=\"menuIcon\"\n              :size=\"$u.addUnit(menuIconSize)\"\n            ></u-icon>\n          </view>\n        </view>\n      </view>\n    </view>\n    <view class=\"u-dropdown__content\">\n      <slot />\n    </view>\n  </view>\n</template>\n\n<script>\nimport props from './props.js';\n/**\n * Dropdown\n * @description\n * @tutorial url\n * @property {String}\n * @event {Function}\n * @example\n */\nexport default {\n  name: 'u-dropdown',\n  mixins: [uni.$u.mixin, props],\n  data() {\n    return {\n      // �˵�����\n      menuList: [],\n      current: 0\n    }\n  },\n  computed: {\n  \n  },\n  created() {\n    // �������������(u-dropdown-item)��this��������data������������������΢��С��������ѭ�����ö�����\n    this.children = [];\n  },\n  methods: {\n    clickHandler(item, index) {\n      this.children.map(child => {\n        if(child.title === item.title) {\n          // this.queryRect('u-dropdown__menu').then(size => {\n          child.$emit('click')\n          child.setContentAnimate(child.show ? 0 : 300)\n          child.show = !child.show\n          // })\n        } else {\n          child.show = false\n          child.setContentAnimate(0)\n        }\n      })\n    },\n    // ��ȡ��ǩ�ĳߴ�λ��\n    queryRect(el) {\n      // #ifndef APP-NVUE\n      // $uGetRectΪuView�Դ��Ľڵ��ѯ�򻯷���������ĵ����ܣ�https://www.uviewui.com/js/getRect.html\n      // ����ڲ�һ����this.$uGetRect�������Ϊthis.$u.getRect�����߹���һ�£����Ʋ�ͬ\n      return new Promise(resolve => {\n        this.$uGetRect(`.${el}`).then(size => {\n          resolve(size)\n        })\n      })\n      // #endif\n      \n      // #ifdef APP-NVUE\n      // nvue�£�ʹ��domģ���ѯԪ�ظ߶�\n      // ����һ��promise���õ��ô˷�����������ʹ��then�ص�\n      return new Promise(resolve => {\n        dom.getComponentRect(this.$refs[el], res => {\n          resolve(res.size)\n        })\n      })\n      // #endif\n    },\n  },\n}\n</script>\n\n<style lang=\"scss\">\n@import '../../libs/css/components.scss';\n\n.u-dropdown {\n  \n  &__menu {\n    @include flex;\n    \n    &__item {\n      flex: 1;\n      @include flex;\n      justify-content: center;\n      \n      &__content {\n        @include flex;\n        align-items: center;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-dropdown-item/props.js",
    "content": "export default {\n    props: {\n        // 当前选中项的value值\n        value: {\n            type: [Number, String, Array],\n            default: ''\n        },\n        // 菜单项标题\n        title: {\n            type: [String, Number],\n            default: ''\n        },\n        // 选项数据，如果传入了默认slot，此参数无效\n        options: {\n            type: Array,\n            default() {\n                return []\n            }\n        },\n        // 是否禁用此菜单项\n        disabled: {\n            type: Boolean,\n            default: false\n        },\n        // 下拉弹窗的高度\n        height: {\n            type: [Number, String],\n            default: 'auto'\n        },\n        // 点击遮罩是否可以收起弹窗\n        closeOnClickOverlay: {\n            type: Boolean,\n            default: true\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue",
    "content": "<template>\n  <view class=\"u-drawdown\">\n    <view\n      class=\"u-dropdown__menu\"\n      :style=\"{\n\t\t\t\theight: $u.addUnit(height)\n\t\t\t}\"\n      ref=\"u-dropdown__menu\"\n    >\n      <view\n        class=\"u-dropdown__menu__item\"\n        v-for=\"(item, index) in menuList\"\n        :key=\"index\"\n        @tap.stop=\"clickHandler(item, index)\"\n      >\n        <view class=\"u-dropdown__menu__item__content\">\n          <text\n            class=\"u-dropdown__menu__item__content__text\"\n            :style=\"[index === current ? activeStyle : inactiveStyle]\"\n          >{{item.title}}</text>\n          <view\n            class=\"u-dropdown__menu__item__content__arrow\"\n            :class=\"[index === current && 'u-dropdown__menu__item__content__arrow--rotate']\"\n          >\n            <u-icon\n              :name=\"menuIcon\"\n              :size=\"$u.addUnit(menuIconSize)\"\n            ></u-icon>\n          </view>\n        </view>\n      </view>\n    </view>\n    <view class=\"u-dropdown__content\">\n      <slot />\n    </view>\n  </view>\n</template>\n\n<script>\nimport props from './props.js';\n/**\n * Dropdown\n * @description\n * @tutorial url\n * @property {String}\n * @event {Function}\n * @example\n */\nexport default {\n  name: 'u-dropdown',\n  mixins: [uni.$u.mixin, props],\n  data() {\n    return {\n      // �˵�����\n      menuList: [],\n      current: 0\n    }\n  },\n  computed: {\n  \n  },\n  created() {\n    // �������������(u-dropdown-item)��this��������data������������������΢��С��������ѭ�����ö�����\n    this.children = [];\n  },\n  methods: {\n    clickHandler(item, index) {\n      this.children.map(child => {\n        if(child.title === item.title) {\n          // this.queryRect('u-dropdown__menu').then(size => {\n          child.$emit('click')\n          child.setContentAnimate(child.show ? 0 : 300)\n          child.show = !child.show\n          // })\n        } else {\n          child.show = false\n          child.setContentAnimate(0)\n        }\n      })\n    },\n    // ��ȡ��ǩ�ĳߴ�λ��\n    queryRect(el) {\n      // #ifndef APP-NVUE\n      // $uGetRectΪuView�Դ��Ľڵ��ѯ�򻯷���������ĵ����ܣ�https://www.uviewui.com/js/getRect.html\n      // ����ڲ�һ����this.$uGetRect�������Ϊthis.$u.getRect�����߹���һ�£����Ʋ�ͬ\n      return new Promise(resolve => {\n        this.$uGetRect(`.${el}`).then(size => {\n          resolve(size)\n        })\n      })\n      // #endif\n      \n      // #ifdef APP-NVUE\n      // nvue�£�ʹ��domģ���ѯԪ�ظ߶�\n      // ����һ��promise���õ��ô˷�����������ʹ��then�ص�\n      return new Promise(resolve => {\n        dom.getComponentRect(this.$refs[el], res => {\n          resolve(res.size)\n        })\n      })\n      // #endif\n    },\n  },\n}\n</script>\n\n<style lang=\"scss\">\n@import '../../libs/css/components.scss';\n\n.u-dropdown {\n  \n  &__menu {\n    @include flex;\n    \n    &__item {\n      flex: 1;\n      @include flex;\n      justify-content: center;\n      \n      &__content {\n        @include flex;\n        align-items: center;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-empty/props.js",
    "content": "export default {\n    props: {\n        // 内置图标名称，或图片路径，建议绝对路径\n        icon: {\n            type: String,\n            default: uni.$u.props.empty.icon\n        },\n        // 提示文字\n        text: {\n            type: String,\n            default: uni.$u.props.empty.text\n        },\n        // 文字颜色\n        textColor: {\n            type: String,\n            default: uni.$u.props.empty.textColor\n        },\n        // 文字大小\n        textSize: {\n            type: [String, Number],\n            default: uni.$u.props.empty.textSize\n        },\n        // 图标的颜色\n        iconColor: {\n            type: String,\n            default: uni.$u.props.empty.iconColor\n        },\n        // 图标的大小\n        iconSize: {\n            type: [String, Number],\n            default: uni.$u.props.empty.iconSize\n        },\n        // 选择预置的图标类型\n        mode: {\n            type: String,\n            default: uni.$u.props.empty.mode\n        },\n        //  图标宽度，单位px\n        width: {\n            type: [String, Number],\n            default: uni.$u.props.empty.width\n        },\n        // 图标高度，单位px\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.empty.height\n        },\n        // 是否显示组件\n        show: {\n            type: Boolean,\n            default: uni.$u.props.empty.show\n        },\n        // 组件距离上一个元素之间的距离，默认px单位\n        marginTop: {\n            type: [String, Number],\n            default: uni.$u.props.empty.marginTop\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-empty/u-empty.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-empty\"\n\t    :style=\"[emptyStyle]\"\n\t    v-if=\"show\"\n\t>\n\t\t<u-icon\n\t\t    v-if=\"!isSrc\"\n\t\t    :name=\"mode === 'message' ? 'chat' : `empty-${mode}`\"\n\t\t    :size=\"iconSize\"\n\t\t    :color=\"iconColor\"\n\t\t    margin-top=\"14\"\n\t\t></u-icon>\n\t\t<image\n\t\t    v-else\n\t\t    :style=\"{\n\t\t\t\twidth: $u.addUnit(width),\n\t\t\t\theight: $u.addUnit(height),\n\t\t\t}\"\n\t\t    :src=\"icon\"\n\t\t    mode=\"widthFix\"\n\t\t></image>\n\t\t<text\n\t\t    class=\"u-empty__text\"\n\t\t    :style=\"[textStyle]\"\n\t\t>{{text ? text : icons[mode]}}</text>\n\t\t<view class=\"u-empty__wrap\" v-if=\"$slots.default || $slots.$default\">\n\t\t\t<slot />\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * empty 内容为空\n\t * @description 该组件用于需要加载内容，但是加载的第一页数据就为空，提示一个\"没有内容\"的场景， 我们精心挑选了十几个场景的图标，方便您使用。\n\t * @tutorial https://www.uviewui.com/components/empty.html\n\t * @property {String}\t\t\ticon\t\t内置图标名称，或图片路径，建议绝对路径\n\t * @property {String}\t\t\ttext\t\t提示文字\n\t * @property {String}\t\t\ttextColor\t文字颜色 (默认 '#c0c4cc' )\n\t * @property {String | Number}\ttextSize\t文字大小 （默认 14 ）\n\t * @property {String}\t\t\ticonColor\t图标的颜色 （默认 '#c0c4cc' ）\n\t * @property {String | Number}\ticonSize\t图标的大小 （默认 90 ）\n\t * @property {String}\t\t\tmode\t\t选择预置的图标类型 （默认 'data' ）\n\t * @property {String | Number}\twidth\t\t图标宽度，单位px （默认 160 ）\n\t * @property {String | Number}\theight\t\t图标高度，单位px （默认 160 ）\n\t * @property {Boolean}\t\t\tshow\t\t是否显示组件 （默认 true ）\n\t * @property {String | Number}\tmarginTop\t组件距离上一个元素之间的距离，默认px单位 （默认 0 ）\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * \n\t * @event {Function} click 点击组件时触发\n\t * @event {Function} close 点击关闭按钮时触发\n\t * @example <u-empty text=\"所谓伊人，在水一方\" mode=\"list\"></u-empty>\n\t */\n\texport default {\n\t\tname: \"u-empty\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\ticons: {\n\t\t\t\t\tcar: '购物车为空',\n\t\t\t\t\tpage: '页面不存在',\n\t\t\t\t\tsearch: '没有搜索结果',\n\t\t\t\t\taddress: '没有收货地址',\n\t\t\t\t\twifi: '没有WiFi',\n\t\t\t\t\torder: '订单为空',\n\t\t\t\t\tcoupon: '没有优惠券',\n\t\t\t\t\tfavor: '暂无收藏',\n\t\t\t\t\tpermission: '无权限',\n\t\t\t\t\thistory: '无历史记录',\n\t\t\t\t\tnews: '无新闻列表',\n\t\t\t\t\tmessage: '消息列表为空',\n\t\t\t\t\tlist: '列表为空',\n\t\t\t\t\tdata: '数据为空',\n\t\t\t\t\tcomment: '暂无评论',\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 组件样式\n\t\t\temptyStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.marginTop = uni.$u.addUnit(this.marginTop)\n\t\t\t\t// 合并customStyle样式，此参数通过mixin中的props传递\n\t\t\t\treturn uni.$u.deepMerge(uni.$u.addStyle(this.customStyle), style)\n\t\t\t},\n\t\t\t// 文本样式\n\t\t\ttextStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.color = this.textColor\n\t\t\t\tstyle.fontSize = uni.$u.addUnit(this.textSize)\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 判断icon是否图片路径\n\t\t\tisSrc() {\n\t\t\t\treturn this.icon.indexOf('/') >= 0\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import '../../libs/css/components.scss';\n\t$u-empty-text-margin-top:20rpx !default;\n\t$u-empty-slot-margin-top:20rpx !default;\n\n\t.u-empty {\n\t\t@include flex;\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\n\t\t&__text {\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\tmargin-top: $u-empty-text-margin-top;\n\t\t}\n\t}\n\t\t.u-slot-wrap {\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\tmargin-top:$u-empty-slot-margin-top;\n\t\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-form/props.js",
    "content": "export default {\n    props: {\n        // 当前form的需要验证字段的集合\n        model: {\n            type: Object,\n            default: uni.$u.props.form.model\n        },\n        // 验证规则\n        rules: {\n            type: [Object, Function, Array],\n            default: uni.$u.props.form.rules\n        },\n        // 有错误时的提示方式，message-提示信息，toast-进行toast提示\n        // border-bottom-下边框呈现红色，none-无提示\n        errorType: {\n            type: String,\n            default: uni.$u.props.form.errorType\n        },\n        // 是否显示表单域的下划线边框\n        borderBottom: {\n            type: Boolean,\n            default: uni.$u.props.form.borderBottom\n        },\n        // label的位置，left-左边，top-上边\n        labelPosition: {\n            type: String,\n            default: uni.$u.props.form.labelPosition\n        },\n        // label的宽度，单位px\n        labelWidth: {\n            type: [String, Number],\n            default: uni.$u.props.form.labelWidth\n        },\n        // lable字体的对齐方式\n        labelAlign: {\n            type: String,\n            default: uni.$u.props.form.labelAlign\n        },\n        // lable的样式，对象形式\n        labelStyle: {\n            type: Object,\n            default: uni.$u.props.form.labelStyle\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-form/u-form.vue",
    "content": "<template>\n\t<view class=\"u-form\">\n\t\t<slot />\n\t</view>\n</template>\n\n<script>\n\timport props from \"./props.js\";\n\timport Schema from \"../../libs/util/async-validator\";\n\t// 去除警告信息\n\tSchema.warning = function() {};\n\t/**\n\t * Form 表单\n\t * @description 此组件一般用于表单场景，可以配置Input输入框，Select弹出框，进行表单验证等。\n\t * @tutorial https://www.uviewui.com/components/form.html\n\t * @property {Object}\t\t\t\t\t\tmodel\t\t\t当前form的需要验证字段的集合\n\t * @property {Object | Function | Array}\trules\t\t\t验证规则\n\t * @property {String}\t\t\t\t\t\terrorType\t\t错误的提示方式，见上方说明 ( 默认 message )\n\t * @property {Boolean}\t\t\t\t\t\tborderBottom\t是否显示表单域的下划线边框   ( 默认 true ）\n\t * @property {String}\t\t\t\t\t\tlabelPosition\t表单域提示文字的位置，left-左侧，top-上方 ( 默认 'left' ）\n\t * @property {String | Number}\t\t\t\tlabelWidth\t\t提示文字的宽度，单位px  ( 默认 45 ）\n\t * @property {String}\t\t\t\t\t\tlabelAlign\t\tlable字体的对齐方式   ( 默认 ‘left' ）\n\t * @property {Object}\t\t\t\t\t\tlabelStyle\t\tlable的样式，对象形式\n\t * @example <u--formlabelPosition=\"left\" :model=\"model1\" :rules=\"rules\" ref=\"form1\"></u--form>\n\t */\n\texport default {\n\t\tname: \"u-form\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tprovide() {\n\t\t\treturn {\n\t\t\t\tuForm: this,\n\t\t\t};\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tformRules: {},\n\t\t\t\t// 规则校验器\n\t\t\t\tvalidator: {},\n\t\t\t\t// 原始的model快照，用于resetFields方法重置表单时使用\n\t\t\t\toriginalModel: null,\n\t\t\t};\n\t\t},\n\t\twatch: {\n\t\t\t// 监听规则的变化\n\t\t\trules: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(n) {\n\t\t\t\t\tthis.setRules(n);\n\t\t\t\t},\n\t\t\t},\n\t\t\t// 监听属性的变化，通知子组件u-form-item重新获取信息\n\t\t\tpropsChange(n) {\n\t\t\t\tif (this.children?.length) {\n\t\t\t\t\tthis.children.map((child) => {\n\t\t\t\t\t\t// 判断子组件(u-form-item)如果有updateParentData方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\n\t\t\t\t\t\ttypeof child.updateParentData == \"function\" &&\n\t\t\t\t\t\t\tchild.updateParentData();\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 监听model的初始值作为重置表单的快照\n\t\t\tmodel: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(n) {\n\t\t\t\t\tif (!this.originalModel) {\n\t\t\t\t\t\tthis.originalModel = uni.$u.deepClone(n);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tcomputed: {\n\t\t\tpropsChange() {\n\t\t\t\treturn [\n\t\t\t\t\tthis.errorType,\n\t\t\t\t\tthis.borderBottom,\n\t\t\t\t\tthis.labelPosition,\n\t\t\t\t\tthis.labelWidth,\n\t\t\t\t\tthis.labelAlign,\n\t\t\t\t\tthis.labelStyle,\n\t\t\t\t];\n\t\t\t},\n\t\t},\n\t\tcreated() {\n\t\t\t// 存储当前form下的所有u-form-item的实例\n\t\t\t// 不能定义在data中，否则微信小程序会造成循环引用而报错\n\t\t\tthis.children = [];\n\t\t},\n\t\tmethods: {\n\t\t\t// 手动设置校验的规则，如果规则中有函数的话，微信小程序中会过滤掉，所以只能手动调用设置规则\n\t\t\tsetRules(rules) {\n\t\t\t\t// 判断是否有规则\n\t\t\t\tif (Object.keys(rules).length === 0) return;\n\t\t\t\tif (process.env.NODE_ENV === 'development' && Object.keys(this.model).length === 0) {\n\t\t\t\t\tuni.$u.error('设置rules，model必须设置！如果已经设置，请刷新页面。');\n\t\t\t\t\treturn;\n\t\t\t\t};\n\t\t\t\tthis.formRules = rules;\n\t\t\t\t// 重新将规则赋予Validator\n\t\t\t\tthis.validator = new Schema(rules);\n\t\t\t},\n\t\t\t// 清空所有u-form-item组件的内容，本质上是调用了u-form-item组件中的resetField()方法\n\t\t\tresetFields() {\n\t\t\t\tthis.resetModel();\n\t\t\t},\n\t\t\t// 重置model为初始值的快照\n\t\t\tresetModel(obj) {\n\t\t\t\t// 历遍所有u-form-item，根据其prop属性，还原model的原始快照\n\t\t\t\tthis.children.map((child) => {\n\t\t\t\t\tconst prop = child?.prop;\n\t\t\t\t\tconst value = uni.$u.getProperty(this.originalModel, prop);\n\t\t\t\t\tuni.$u.setProperty(this.model, prop, value);\n\t\t\t\t});\n\t\t\t},\n\t\t\t// 清空校验结果\n\t\t\tclearValidate(props) {\n\t\t\t\tprops = [].concat(props);\n\t\t\t\tthis.children.map((child) => {\n\t\t\t\t\t// 如果u-form-item的prop在props数组中，则清除对应的校验结果信息\n\t\t\t\t\tif (props[0] === undefined || props.includes(child.prop)) {\n\t\t\t\t\t\tchild.message = null;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t},\n\t\t\t// 对部分表单字段进行校验\n\t\t\tasync validateField(value, callback, event = null) {\n\t\t\t\t// $nextTick是必须的，否则model的变更，可能会延后于此方法的执行\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t// 校验错误信息，返回给回调方法，用于存放所有form-item的错误信息\n\t\t\t\t\tconst errorsRes = [];\n\t\t\t\t\t// 如果为字符串，转为数组\n\t\t\t\t\tvalue = [].concat(value);\n\t\t\t\t\t// 历遍children所有子form-item\n\t\t\t\t\tthis.children.map((child) => {\n\t\t\t\t\t\t// 用于存放form-item的错误信息\n\t\t\t\t\t\tconst childErrors = [];\n\t\t\t\t\t\tif (value.includes(child.prop)) {\n\t\t\t\t\t\t\t// 获取对应的属性，通过类似'a.b.c'的形式\n\t\t\t\t\t\t\tconst propertyVal = uni.$u.getProperty(\n\t\t\t\t\t\t\t\tthis.model,\n\t\t\t\t\t\t\t\tchild.prop\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t// 属性链数组\n\t\t\t\t\t\t\tconst propertyChain = child.prop.split(\".\");\n\t\t\t\t\t\t\tconst propertyName =\n\t\t\t\t\t\t\t\tpropertyChain[propertyChain.length - 1];\n\n\t\t\t\t\t\t\tconst rule = this.formRules[child.prop];\n\t\t\t\t\t\t\t// 如果不存在对应的规则，直接返回，否则校验器会报错\n\t\t\t\t\t\t\tif (!rule) return;\n\t\t\t\t\t\t\t// rule规则可为数组形式，也可为对象形式，此处拼接成为数组\n\t\t\t\t\t\t\tconst rules = [].concat(rule);\n\n\t\t\t\t\t\t\t// 对rules数组进行校验\n\t\t\t\t\t\t\tfor (let i = 0; i < rules.length; i++) {\n\t\t\t\t\t\t\t\tconst ruleItem = rules[i];\n\t\t\t\t\t\t\t\t// 将u-form-item的触发器转为数组形式\n\t\t\t\t\t\t\t\tconst trigger = [].concat(ruleItem?.trigger);\n\t\t\t\t\t\t\t\t// 如果是有传入触发事件，但是此form-item却没有配置此触发器的话，不执行校验操作\n\t\t\t\t\t\t\t\tif (event && !trigger.includes(event)) continue;\n\t\t\t\t\t\t\t\t// 实例化校验对象，传入构造规则\n\t\t\t\t\t\t\t\tconst validator = new Schema({\n\t\t\t\t\t\t\t\t\t[propertyName]: ruleItem,\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\tvalidator.validate({\n\t\t\t\t\t\t\t\t\t\t[propertyName]: propertyVal,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t(errors, fields) => {\n\t\t\t\t\t\t\t\t\t\tif (uni.$u.test.array(errors)) {\n\t\t\t\t\t\t\t\t\t\t\terrorsRes.push(...errors);\n\t\t\t\t\t\t\t\t\t\t\tchildErrors.push(...errors);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tchild.message =\n\t\t\t\t\t\t\t\t\t\t\tchildErrors[0]?.message ?? null;\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\t// 执行回调函数\n\t\t\t\t\ttypeof callback === \"function\" && callback(errorsRes);\n\t\t\t\t});\n\t\t\t},\n\t\t\t// 校验全部数据\n\t\t\tvalidate(callback) {\n\t\t\t\t// 开发环境才提示，生产环境不会提示\n\t\t\t\tif (process.env.NODE_ENV === 'development' && Object.keys(this.formRules).length === 0) {\n\t\t\t\t\tuni.$u.error('未设置rules，请看文档说明！如果已经设置，请刷新页面。');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t\t// $nextTick是必须的，否则model的变更，可能会延后于validate方法\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\t// 获取所有form-item的prop，交给validateField方法进行校验\n\t\t\t\t\t\tconst formItemProps = this.children.map(\n\t\t\t\t\t\t\t(item) => item.prop\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthis.validateField(formItemProps, (errors) => {\n\t\t\t\t\t\t\tif(errors.length) {\n\t\t\t\t\t\t\t\t// 如果错误提示方式为toast，则进行提示\n\t\t\t\t\t\t\t\tthis.errorType === 'toast' && uni.$u.toast(errors[0].message)\n\t\t\t\t\t\t\t\treject(errors)\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tresolve(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\t};\n</script>\n\n<style lang=\"scss\" scoped>\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-form-item/props.js",
    "content": "export default {\n    props: {\n        // input的label提示语\n        label: {\n            type: String,\n            default: uni.$u.props.formItem.label\n        },\n        // 绑定的值\n        prop: {\n            type: String,\n            default: uni.$u.props.formItem.prop\n        },\n        // 是否显示表单域的下划线边框\n        borderBottom: {\n            type: [String, Boolean],\n            default: uni.$u.props.formItem.borderBottom\n        },\n        // label的位置，left-左边，top-上边\n        labelPosition: {\n            type: String,\n            default: uni.$u.props.formItem.labelPosition\n        },\n        // label的宽度，单位px\n        labelWidth: {\n            type: [String, Number],\n            default: uni.$u.props.formItem.labelWidth\n        },\n        // 右侧图标\n        rightIcon: {\n            type: String,\n            default: uni.$u.props.formItem.rightIcon\n        },\n        // 左侧图标\n        leftIcon: {\n            type: String,\n            default: uni.$u.props.formItem.leftIcon\n        },\n        // 是否显示左边的必填星号，只作显示用，具体校验必填的逻辑，请在rules中配置\n        required: {\n            type: Boolean,\n            default: uni.$u.props.formItem.required\n        },\n        leftIconStyle: {\n            type: [String, Object],\n            default: uni.$u.props.formItem.leftIconStyle,\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-form-item/u-form-item.vue",
    "content": "<template>\n\t<view class=\"u-form-item\">\n\t\t<view\n\t\t\tclass=\"u-form-item__body\"\n\t\t\t@tap=\"clickHandler\"\n\t\t\t:style=\"[$u.addStyle(customStyle), {\n\t\t\t\tflexDirection: (labelPosition || parentData.labelPosition) === 'left' ? 'row' : 'column'\n\t\t\t}]\"\n\t\t>\n\t\t\t<!-- 微信小程序中，将一个参数设置空字符串，结果会变成字符串\"true\" -->\n\t\t\t<slot name=\"label\">\n\t\t\t\t<!-- {{required}} -->\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-form-item__body__left\"\n\t\t\t\t\tv-if=\"required || leftIcon || label\"\n\t\t\t\t\t:style=\"{\n\t\t\t\t\t\twidth: $u.addUnit(labelWidth || parentData.labelWidth),\n\t\t\t\t\t\tmarginBottom: parentData.labelPosition === 'left' ? 0 : '5px',\n\t\t\t\t\t}\"\n\t\t\t\t>\n\t\t\t\t\t<!-- 为了块对齐 -->\n\t\t\t\t\t<view class=\"u-form-item__body__left__content\">\n\t\t\t\t\t\t<!-- nvue不支持伪元素before -->\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tv-if=\"required\"\n\t\t\t\t\t\t\tclass=\"u-form-item__body__left__content__required\"\n\t\t\t\t\t\t>*</text>\n\t\t\t\t\t\t<view\n\t\t\t\t\t\t\tclass=\"u-form-item__body__left__content__icon\"\n\t\t\t\t\t\t\tv-if=\"leftIcon\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t\t\t:name=\"leftIcon\"\n\t\t\t\t\t\t\t\t:custom-style=\"leftIconStyle\"\n\t\t\t\t\t\t\t></u-icon>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tclass=\"u-form-item__body__left__content__label\"\n\t\t\t\t\t\t\t:style=\"[parentData.labelStyle, {\n\t\t\t\t\t\t\t\tjustifyContent: parentData.labelAlign === 'left' ? 'flex-start' : parentData.labelAlign === 'center' ? 'center' : 'flex-end'\n\t\t\t\t\t\t\t}]\"\n\t\t\t\t\t\t>{{ label }}</text>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</slot>\n\t\t\t<view class=\"u-form-item__body__right\">\n\t\t\t\t<view class=\"u-form-item__body__right__content\">\n\t\t\t\t\t<view class=\"u-form-item__body__right__content__slot\">\n\t\t\t\t\t\t<slot />\n\t\t\t\t\t</view>\n\t\t\t\t\t<view\n\t\t\t\t\t\tclass=\"item__body__right__content__icon\"\n\t\t\t\t\t\tv-if=\"$slots.right\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<slot name=\"right\" />\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t\t<slot name=\"error\">\n\t\t\t<text\n\t\t\t\tv-if=\"!!message && parentData.errorType === 'message'\"\n\t\t\t\tclass=\"u-form-item__body__right__message\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tmarginLeft:  $u.addUnit(parentData.labelPosition === 'top' ? 0 : (labelWidth || parentData.labelWidth))\n\t\t\t\t}\"\n\t\t\t>{{ message }}</text>\n\t\t</slot>\n\t\t<u-line\n\t\t\tv-if=\"borderBottom\"\n\t\t\t:color=\"message && parentData.errorType === 'border-bottom' ? $u.color.error : propsLine.color\"\n\t\t\t:customStyle=\"`margin-top: ${message && parentData.errorType === 'message' ? '5px' : 0}`\"\n\t\t></u-line>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Form 表单\n\t * @description 此组件一般用于表单场景，可以配置Input输入框，Select弹出框，进行表单验证等。\n\t * @tutorial https://www.uviewui.com/components/form.html\n\t * @property {String}\t\t\tlabel\t\t\tinput的label提示语\n\t * @property {String}\t\t\tprop\t\t\t绑定的值\n\t * @property {String | Boolean}\tborderBottom\t是否显示表单域的下划线边框\n\t * @property {String | Number}\tlabelWidth\t\tlabel的宽度，单位px\n\t * @property {String}\t\t\trightIcon\t\t右侧图标\n\t * @property {String}\t\t\tleftIcon\t\t左侧图标\n\t * @property {String | Object} leftIconStyle 左侧图标的样式\n\t * @property {Boolean}\t\t\trequired\t\t是否显示左边的必填星号，只作显示用，具体校验必填的逻辑，请在rules中配置 (默认 false )\n\t *\n\t * @example <u-form-item label=\"姓名\" prop=\"userInfo.name\" borderBottom ref=\"item1\"></u-form-item>\n\t */\n\texport default {\n\t\tname: 'u-form-item',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 错误提示语\n\t\t\t\tmessage: '',\n\t\t\t\tparentData: {\n\t\t\t\t\t// 提示文本的位置\n\t\t\t\t\tlabelPosition: 'left',\n\t\t\t\t\t// 提示文本对齐方式\n\t\t\t\t\tlabelAlign: 'left',\n\t\t\t\t\t// 提示文本的样式\n\t\t\t\t\tlabelStyle: {},\n\t\t\t\t\t// 提示文本的宽度\n\t\t\t\t\tlabelWidth: 45,\n\t\t\t\t\t// 错误提示方式\n\t\t\t\t\terrorType: 'message'\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// 组件创建完成时，将当前实例保存到u-form中\n\t\tcomputed: {\n\t\t\tpropsLine() {\n\t\t\t\treturn uni.$u.props.line\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 父组件的实例\n\t\t\t\tthis.updateParentData()\n\t\t\t\tif (!this.parent) {\n\t\t\t\t\tuni.$u.error('u-form-item需要结合u-form组件使用')\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 获取父组件的参数\n\t\t\tupdateParentData() {\n\t\t\t\t// 此方法写在mixin中\n\t\t\t\tthis.getParentData('u-form');\n\t\t\t},\n\t\t\t// 移除u-form-item的校验结果\n\t\t\tclearValidate() {\n\t\t\t\tthis.message = null\n\t\t\t},\n\t\t\t// 清空当前的组件的校验结果，并重置为初始值\n\t\t\tresetField() {\n\t\t\t\t// 找到原始值\n\t\t\t\tconst value = uni.$u.getProperty(this.parent.originalModel, this.prop)\n\t\t\t\t// 将u-form的model的prop属性链还原原始值\n\t\t\t\tuni.$u.setProperty(this.parent.model, this.prop, value)\n\t\t\t\t// 移除校验结果\n\t\t\t\tthis.message = null\n\t\t\t},\n\t\t\t// 点击组件\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('click')\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-form-item {\n\t\t@include flex(column);\n\t\tfont-size: 14px;\n\t\tcolor: $u-main-color;\n\n\t\t&__body {\n\t\t\t@include flex;\n\t\t\tpadding: 10px 0;\n\n\t\t\t&__left {\n\t\t\t\t@include flex;\n\t\t\t\talign-items: center;\n\n\t\t\t\t&__content {\n\t\t\t\t\tposition: relative;\n\t\t\t\t\t@include flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tpadding-right: 10rpx;\n\t\t\t\t\tflex: 1;\n\n\t\t\t\t\t&__icon {\n\t\t\t\t\t\tmargin-right: 8rpx;\n\t\t\t\t\t}\n\n\t\t\t\t\t&__required {\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tleft: -9px;\n\t\t\t\t\t\tcolor: $u-error;\n\t\t\t\t\t\tline-height: 20px;\n\t\t\t\t\t\tfont-size: 20px;\n\t\t\t\t\t\ttop: 3px;\n\t\t\t\t\t}\n\n\t\t\t\t\t&__label {\n\t\t\t\t\t\t@include flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\tcolor: $u-main-color;\n\t\t\t\t\t\tfont-size: 15px;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&__right {\n\t\t\t\tflex: 1;\n\n\t\t\t\t&__content {\n\t\t\t\t\t@include flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tflex: 1;\n\n\t\t\t\t\t&__slot {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\t/* #ifndef MP */\n\t\t\t\t\t\t@include flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\t/* #endif */\n\t\t\t\t\t}\n\n\t\t\t\t\t&__icon {\n\t\t\t\t\t\tmargin-left: 10rpx;\n\t\t\t\t\t\tcolor: $u-light-color;\n\t\t\t\t\t\tfont-size: 30rpx;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&__message {\n\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\tline-height: 12px;\n\t\t\t\t\tcolor: $u-error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-gap/props.js",
    "content": "export default {\n    props: {\n        // 背景颜色（默认transparent）\n        bgColor: {\n            type: String,\n            default: uni.$u.props.gap.bgColor\n        },\n        // 分割槽高度，单位px（默认30）\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.gap.height\n        },\n        // 与上一个组件的距离\n        marginTop: {\n            type: [String, Number],\n            default: uni.$u.props.gap.marginTop\n        },\n        // 与下一个组件的距离\n        marginBottom: {\n            type: [String, Number],\n            default: uni.$u.props.gap.marginBottom\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-gap/u-gap.vue",
    "content": "<template>\n\t<view class=\"u-gap\" :style=\"[gapStyle]\"></view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * gap 间隔槽\n\t * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景，方便用户风格统一，减少工作量\n\t * @tutorial https://www.uviewui.com/components/gap.html\n\t * @property {String}\t\t\tbgColor\t\t\t背景颜色 （默认 'transparent' ）\n\t * @property {String | Number}\theight\t\t\t分割槽高度，单位px （默认 20 ）\n\t * @property {String | Number}\tmarginTop\t\t与前一个组件的距离，单位px（ 默认 0 ）\n\t * @property {String | Number}\tmarginBottom\t与后一个组件的距离，单位px （默认 0 ）\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\n\t * \n\t * @example <u-gap height=\"80\" bg-color=\"#bbb\"></u-gap>\n\t */\n\texport default {\n\t\tname: \"u-gap\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\tgapStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tbackgroundColor: this.bgColor,\n\t\t\t\t\theight: uni.$u.addUnit(this.height),\n\t\t\t\t\tmarginTop: uni.$u.addUnit(this.marginTop),\n\t\t\t\t\tmarginBottom: uni.$u.addUnit(this.marginBottom),\n\t\t\t\t}\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-grid/props.js",
    "content": "export default {\n    props: {\n        // 分成几列\n        col: {\n            type: [String, Number],\n            default: uni.$u.props.grid.col\n        },\n        // 是否显示边框\n        border: {\n            type: Boolean,\n            default: uni.$u.props.grid.border\n        },\n        // 宫格对齐方式，表现为数量少的时候，靠左，居中，还是靠右\n        align: {\n            type: String,\n            default: uni.$u.props.grid.align\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-grid/u-grid.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-grid\"\n\t\tref='u-grid'\n\t    :style=\"[gridStyle]\"\n\t>\n\t\t<slot />\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * grid 宫格布局\n\t * @description 宫格组件一般用于同时展示多个同类项目的场景，可以给宫格的项目设置徽标组件(badge)，或者图标等，也可以扩展为左右滑动的轮播形式。\n\t * @tutorial https://www.uviewui.com/components/grid.html\n\t * @property {String | Number}\tcol\t\t\t宫格的列数（默认 3 ）\n\t * @property {Boolean}\t\t\tborder\t\t是否显示宫格的边框（默认 false ）\n\t * @property {String}\t\t\talign\t\t宫格对齐方式，表现为数量少的时候，靠左，居中，还是靠右 （默认 'left' ）\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * @event {Function} click 点击宫格触发\n\t * @example <u-grid :col=\"3\" @click=\"click\"></u-grid>\n\t */\n\texport default {\n\t\tname: 'u-grid',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tindex: 0,\n\t\t\t\twidth: 0\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\n\t\t\tparentData() {\n\t\t\t\tif (this.children.length) {\n\t\t\t\t\tthis.children.map(child => {\n\t\t\t\t\t\t// 判断子组件(u-radio)如果有updateParentData方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\n\t\t\t\t\t\ttypeof(child.updateParentData) == 'function' && child.updateParentData();\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tcreated() {\n\t\t\t// 如果将children定义在data中，在微信小程序会造成循环引用而报错\n\t\t\tthis.children = []\n\t\t},\n\t\tcomputed: {\n\t\t\t// 计算父组件的值是否发生变化\n\t\t\tparentData() {\n\t\t\t\treturn [this.hoverClass, this.col, this.size, this.border];\n\t\t\t},\n\t\t\t// 宫格对齐方式\n\t\t\tgridStyle() {\n\t\t\t\tlet style = {};\n\t\t\t\tswitch (this.align) {\n\t\t\t\t\tcase 'left':\n\t\t\t\t\t\tstyle.justifyContent = 'flex-start';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'center':\n\t\t\t\t\t\tstyle.justifyContent = 'center';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'right':\n\t\t\t\t\t\tstyle.justifyContent = 'flex-end';\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tstyle.justifyContent = 'flex-start';\n\t\t\t\t};\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 此方法由u-grid-item触发，用于在u-grid发出事件\n\t\t\tchildClick(name) {\n\t\t\t\tthis.$emit('click', name)\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n     $u-grid-width:100% !default;\n\t.u-grid {\n\t\t/* #ifdef MP */\n\t\twidth: $u-grid-width;\n\t\tposition: relative;\n\t\tbox-sizing: border-box;\n\t\toverflow: hidden;\n\t\tdisplay: block;\n\t\t/* #endif */\n\t\tjustify-content: center;\n\t\t@include flex;\n\t\tflex-wrap: wrap;\n\t\talign-items: center;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-grid-item/props.js",
    "content": "export default {\n    props: {\n        // 宫格的name\n        name: {\n            type: [String, Number, null],\n            default: uni.$u.props.gridItem.name\n        },\n        // 背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.gridItem.bgColor\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-grid-item/u-grid-item.vue",
    "content": "<template>\n\t<!-- #ifndef APP-NVUE -->\n\t<view\n\t    class=\"u-grid-item\"\n\t    hover-class=\"u-grid-item--hover-class\"\n\t    :hover-stay-time=\"200\"\n\t    @tap=\"clickHandler\"\n\t    :class=\"classes\"\n\t    :style=\"[itemStyle]\"\n\t>\n\t\t<slot />\n\t</view>\n\t<!-- #endif -->\n\t<!-- #ifdef APP-NVUE -->\n\t<view\n\t    class=\"u-grid-item\"\n\t    :hover-stay-time=\"200\"\n\t    @tap=\"clickHandler\"\n\t    :class=\"classes\"\n\t    :style=\"[itemStyle]\"\n\t>\n\t\t<slot />\n\t</view>\n\t<!-- #endif -->\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * gridItem 提示\n\t * @description 宫格组件一般用于同时展示多个同类项目的场景，可以给宫格的项目设置徽标组件(badge)，或者图标等，也可以扩展为左右滑动的轮播形式。搭配u-grid使用\n\t * @tutorial https://www.uviewui.com/components/grid.html\n\t * @property {String | Number}\tname\t\t宫格的name ( 默认 null )\n\t * @property {String}\t\t\tbgColor\t\t宫格的背景颜色 （默认 'transparent' ）\n\t * @property {Object}\t\t\tcustomStyle\t自定义样式，对象形式\n\t * @event {Function} click 点击宫格触发\n\t * @example <u-grid-item></u-grid-item>\n\t */\n\texport default {\n\t\tname: \"u-grid-item\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tparentData: {\n\t\t\t\t\tcol: 3, // 父组件划分的宫格数\n\t\t\t\t\tborder: true, // 是否显示边框，根据父组件决定\n\t\t\t\t},\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\twidth: 0, // nvue下才这么计算，vue下放到computed中，否则会因为延时造成闪烁\n\t\t\t\t// #endif\n\t\t\t\tclasses: [], // 类名集合，用于判断是否显示右边和下边框\n\t\t\t};\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tcomputed: {\n\t\t\t// #ifndef APP-NVUE\n\t\t\t// vue下放到computed中，否则会因为延时造成闪烁\n\t\t\twidth() {\n\t\t\t\treturn 100 / Number(this.parentData.col) + '%'\n\t\t\t},\n\t\t\t// #endif\n\t\t\titemStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tbackground: this.bgColor,\n\t\t\t\t\twidth: this.width\n\t\t\t\t}\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 用于在父组件u-grid的children中被添加入子组件时，\n\t\t\t\t// 重新计算item的边框\n\t\t\t\tuni.$on('$uGridItem', () => {\n\t\t\t\t\tthis.gridItemClasses()\n\t\t\t\t})\n\t\t\t\t// 父组件的实例\n\t\t\t\tthis.updateParentData()\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// 获取元素该有的长度，nvue下要延时才准确\n\t\t\t\tthis.$nextTick(function(){\n\t\t\t\t\tthis.getItemWidth()\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t\t// 发出事件，通知所有的grid-item都重新计算自己的边框\n\t\t\t\tuni.$emit('$uGridItem')\n\t\t\t\tthis.gridItemClasses()\n\t\t\t},\n\t\t\t// 获取父组件的参数\n\t\t\tupdateParentData() {\n\t\t\t\t// 此方法写在mixin中\n\t\t\t\tthis.getParentData('u-grid');\n\t\t\t},\n\t\t\tclickHandler() {\n\t\t\t\tlet name = this.name\n\t\t\t\t// 如果没有设置name属性，历遍父组件的children数组，判断当前的元素是否和本实例this相等，找出当前组件的索引\n\t\t\t\tconst children = this.parent?.children\n\t\t\t\tif(children && this.name === null) {\n\t\t\t\t\tname = children.findIndex(child => child === this)\n\t\t\t\t}\n\t\t\t\t// 调用父组件方法，发出事件\n\t\t\t\tthis.parent && this.parent.childClick(name)\n\t\t\t\tthis.$emit('click', name)\n\t\t\t},\n\t\t\tasync getItemWidth() {\n\t\t\t\t// 如果是nvue，不能使用百分比，只能使用固定宽度\n\t\t\t\tlet width = 0\n\t\t\t\tif(this.parent) {\n\t\t\t\t\t// 获取父组件宽度后，除以栅格数，得出每个item的宽度\n\t\t\t\t\tconst parentWidth = await this.getParentWidth()\n\t\t\t\t\twidth = parentWidth / Number(this.parentData.col) + 'px'\n\t\t\t\t}\n\t\t\t\tthis.width = width\n\t\t\t},\n\t\t\t// 获取父元素的尺寸\n\t\t\tgetParentWidth() {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// 返回一个promise，让调用者可以用await同步获取\n\t\t\t\tconst dom = uni.requireNativePlugin('dom')\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t// 调用父组件的ref\n\t\t\t\t\tdom.getComponentRect(this.parent.$refs['u-grid'], res => {\n\t\t\t\t\t\tresolve(res.size.width)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\tgridItemClasses() {\n\t\t\t\tif(this.parentData.border) {\n\t\t\t\t\tconst classes = []\n\t\t\t\t\tthis.parent.children.map((child, index) =>{\n\t\t\t\t\t\tif(this === child) {\n\t\t\t\t\t\t\tconst len = this.parent.children.length\n\t\t\t\t\t\t\t// 贴近右边屏幕边沿的child，并且最后一个（比如只有横向2个的时候），无需右边框\n\t\t\t\t\t\t\tif((index + 1) % this.parentData.col !== 0 && index + 1 !== len) {\n\t\t\t\t\t\t\t\tclasses.push('u-border-right')\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// 总的宫格数量对列数取余的值\n\t\t\t\t\t\t\t// 如果取余后，值为0，则意味着要将最后一排的宫格，都不需要下边框\n\t\t\t\t\t\t\tconst lessNum = len % this.parentData.col === 0 ? this.parentData.col : len % this.parentData.col\n\t\t\t\t\t\t\t// 最下面的一排child，无需下边框\n\t\t\t\t\t\t\tif(index < len - lessNum) {\n\t\t\t\t\t\t\t\tclasses.push('u-border-bottom')\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// 支付宝，头条小程序无法动态绑定一个数组类名，否则解析出来的结果会带有\",\"，而导致失效\n\t\t\t\t\t// #ifdef MP-ALIPAY || MP-TOUTIAO\n\t\t\t\t\tclasses = classes.join(' ')\n\t\t\t\t\t// #endif\n\t\t\t\t\tthis.classes = classes\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tbeforeDestroy() {\n\t\t\t// 移除事件监听，释放性能\n\t\t\tuni.$off('$uGridItem')\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n      $u-grid-item-hover-class-opcatiy:.5 !default;\n      $u-grid-item-margin-top:1rpx !default;\n      $u-grid-item-border-right-width:0.5px !default;\n      $u-grid-item-border-bottom-width:0.5px !default;\n      $u-grid-item-border-right-color:$u-border-color !default;\n      $u-grid-item-border-bottom-color:$u-border-color !default;\n\t.u-grid-item {\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tposition: relative;\n\t\tflex-direction: column;\n\t\t/* #ifndef APP-NVUE */\n\t\tbox-sizing: border-box;\n\t\tdisplay: flex;\n\t\t/* #endif */\n\n\t\t/* #ifdef MP */\n\t\tposition: relative;\n\t\tfloat: left;\n\t\t/* #endif */\n\n\t\t/* #ifdef MP-WEIXIN */\n\t\tmargin-top:$u-grid-item-margin-top;\n\t\t/* #endif */\n\n\t\t&--hover-class {\n\t\t\topacity:$u-grid-item-hover-class-opcatiy;\n\t\t}\n\t}\n\n\t/* #ifdef APP-NVUE */\n\t// 由于nvue不支持组件内引入app.vue中再引入的样式，所以需要写在这里\n\t.u-border-right {\n\t\tborder-right-width:$u-grid-item-border-right-width;\n\t\tborder-color: $u-grid-item-border-right-color;\n\t}\n\n\t.u-border-bottom {\n\t\tborder-bottom-width:$u-grid-item-border-bottom-width;\n\t\tborder-color:$u-grid-item-border-bottom-color;\n\t}\n\n\t/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-icon/icons.js",
    "content": "export default {\n    'uicon-level': '\\ue693',\n    'uicon-column-line': '\\ue68e',\n    'uicon-checkbox-mark': '\\ue807',\n    'uicon-folder': '\\ue7f5',\n    'uicon-movie': '\\ue7f6',\n    'uicon-star-fill': '\\ue669',\n    'uicon-star': '\\ue65f',\n    'uicon-phone-fill': '\\ue64f',\n    'uicon-phone': '\\ue622',\n    'uicon-apple-fill': '\\ue881',\n    'uicon-chrome-circle-fill': '\\ue885',\n    'uicon-backspace': '\\ue67b',\n    'uicon-attach': '\\ue632',\n    'uicon-cut': '\\ue948',\n    'uicon-empty-car': '\\ue602',\n    'uicon-empty-coupon': '\\ue682',\n    'uicon-empty-address': '\\ue646',\n    'uicon-empty-favor': '\\ue67c',\n    'uicon-empty-permission': '\\ue686',\n    'uicon-empty-news': '\\ue687',\n    'uicon-empty-search': '\\ue664',\n    'uicon-github-circle-fill': '\\ue887',\n    'uicon-rmb': '\\ue608',\n    'uicon-person-delete-fill': '\\ue66a',\n    'uicon-reload': '\\ue788',\n    'uicon-order': '\\ue68f',\n    'uicon-server-man': '\\ue6bc',\n    'uicon-search': '\\ue62a',\n    'uicon-fingerprint': '\\ue955',\n    'uicon-more-dot-fill': '\\ue630',\n    'uicon-scan': '\\ue662',\n    'uicon-share-square': '\\ue60b',\n    'uicon-map': '\\ue61d',\n    'uicon-map-fill': '\\ue64e',\n    'uicon-tags': '\\ue629',\n    'uicon-tags-fill': '\\ue651',\n    'uicon-bookmark-fill': '\\ue63b',\n    'uicon-bookmark': '\\ue60a',\n    'uicon-eye': '\\ue613',\n    'uicon-eye-fill': '\\ue641',\n    'uicon-mic': '\\ue64a',\n    'uicon-mic-off': '\\ue649',\n    'uicon-calendar': '\\ue66e',\n    'uicon-calendar-fill': '\\ue634',\n    'uicon-trash': '\\ue623',\n    'uicon-trash-fill': '\\ue658',\n    'uicon-play-left': '\\ue66d',\n    'uicon-play-right': '\\ue610',\n    'uicon-minus': '\\ue618',\n    'uicon-plus': '\\ue62d',\n    'uicon-info': '\\ue653',\n    'uicon-info-circle': '\\ue7d2',\n    'uicon-info-circle-fill': '\\ue64b',\n    'uicon-question': '\\ue715',\n    'uicon-error': '\\ue6d3',\n    'uicon-close': '\\ue685',\n    'uicon-checkmark': '\\ue6a8',\n    'uicon-android-circle-fill': '\\ue67e',\n    'uicon-android-fill': '\\ue67d',\n    'uicon-ie': '\\ue87b',\n    'uicon-IE-circle-fill': '\\ue889',\n    'uicon-google': '\\ue87a',\n    'uicon-google-circle-fill': '\\ue88a',\n    'uicon-setting-fill': '\\ue872',\n    'uicon-setting': '\\ue61f',\n    'uicon-minus-square-fill': '\\ue855',\n    'uicon-plus-square-fill': '\\ue856',\n    'uicon-heart': '\\ue7df',\n    'uicon-heart-fill': '\\ue851',\n    'uicon-camera': '\\ue7d7',\n    'uicon-camera-fill': '\\ue870',\n    'uicon-more-circle': '\\ue63e',\n    'uicon-more-circle-fill': '\\ue645',\n    'uicon-chat': '\\ue620',\n    'uicon-chat-fill': '\\ue61e',\n    'uicon-bag-fill': '\\ue617',\n    'uicon-bag': '\\ue619',\n    'uicon-error-circle-fill': '\\ue62c',\n    'uicon-error-circle': '\\ue624',\n    'uicon-close-circle': '\\ue63f',\n    'uicon-close-circle-fill': '\\ue637',\n    'uicon-checkmark-circle': '\\ue63d',\n    'uicon-checkmark-circle-fill': '\\ue635',\n    'uicon-question-circle-fill': '\\ue666',\n    'uicon-question-circle': '\\ue625',\n    'uicon-share': '\\ue631',\n    'uicon-share-fill': '\\ue65e',\n    'uicon-shopping-cart': '\\ue621',\n    'uicon-shopping-cart-fill': '\\ue65d',\n    'uicon-bell': '\\ue609',\n    'uicon-bell-fill': '\\ue640',\n    'uicon-list': '\\ue650',\n    'uicon-list-dot': '\\ue616',\n    'uicon-zhihu': '\\ue6ba',\n    'uicon-zhihu-circle-fill': '\\ue709',\n    'uicon-zhifubao': '\\ue6b9',\n    'uicon-zhifubao-circle-fill': '\\ue6b8',\n    'uicon-weixin-circle-fill': '\\ue6b1',\n    'uicon-weixin-fill': '\\ue6b2',\n    'uicon-twitter-circle-fill': '\\ue6ab',\n    'uicon-twitter': '\\ue6aa',\n    'uicon-taobao-circle-fill': '\\ue6a7',\n    'uicon-taobao': '\\ue6a6',\n    'uicon-weibo-circle-fill': '\\ue6a5',\n    'uicon-weibo': '\\ue6a4',\n    'uicon-qq-fill': '\\ue6a1',\n    'uicon-qq-circle-fill': '\\ue6a0',\n    'uicon-moments-circel-fill': '\\ue69a',\n    'uicon-moments': '\\ue69b',\n    'uicon-qzone': '\\ue695',\n    'uicon-qzone-circle-fill': '\\ue696',\n    'uicon-baidu-circle-fill': '\\ue680',\n    'uicon-baidu': '\\ue681',\n    'uicon-facebook-circle-fill': '\\ue68a',\n    'uicon-facebook': '\\ue689',\n    'uicon-car': '\\ue60c',\n    'uicon-car-fill': '\\ue636',\n    'uicon-warning-fill': '\\ue64d',\n    'uicon-warning': '\\ue694',\n    'uicon-clock-fill': '\\ue638',\n    'uicon-clock': '\\ue60f',\n    'uicon-edit-pen': '\\ue612',\n    'uicon-edit-pen-fill': '\\ue66b',\n    'uicon-email': '\\ue611',\n    'uicon-email-fill': '\\ue642',\n    'uicon-minus-circle': '\\ue61b',\n    'uicon-minus-circle-fill': '\\ue652',\n    'uicon-plus-circle': '\\ue62e',\n    'uicon-plus-circle-fill': '\\ue661',\n    'uicon-file-text': '\\ue663',\n    'uicon-file-text-fill': '\\ue665',\n    'uicon-pushpin': '\\ue7e3',\n    'uicon-pushpin-fill': '\\ue86e',\n    'uicon-grid': '\\ue673',\n    'uicon-grid-fill': '\\ue678',\n    'uicon-play-circle': '\\ue647',\n    'uicon-play-circle-fill': '\\ue655',\n    'uicon-pause-circle-fill': '\\ue654',\n    'uicon-pause': '\\ue8fa',\n    'uicon-pause-circle': '\\ue643',\n    'uicon-eye-off': '\\ue648',\n    'uicon-eye-off-outline': '\\ue62b',\n    'uicon-gift-fill': '\\ue65c',\n    'uicon-gift': '\\ue65b',\n    'uicon-rmb-circle-fill': '\\ue657',\n    'uicon-rmb-circle': '\\ue677',\n    'uicon-kefu-ermai': '\\ue656',\n    'uicon-server-fill': '\\ue751',\n    'uicon-coupon-fill': '\\ue8c4',\n    'uicon-coupon': '\\ue8ae',\n    'uicon-integral': '\\ue704',\n    'uicon-integral-fill': '\\ue703',\n    'uicon-home-fill': '\\ue964',\n    'uicon-home': '\\ue965',\n    'uicon-hourglass-half-fill': '\\ue966',\n    'uicon-hourglass': '\\ue967',\n    'uicon-account': '\\ue628',\n    'uicon-plus-people-fill': '\\ue626',\n    'uicon-minus-people-fill': '\\ue615',\n    'uicon-account-fill': '\\ue614',\n    'uicon-thumb-down-fill': '\\ue726',\n    'uicon-thumb-down': '\\ue727',\n    'uicon-thumb-up': '\\ue733',\n    'uicon-thumb-up-fill': '\\ue72f',\n    'uicon-lock-fill': '\\ue979',\n    'uicon-lock-open': '\\ue973',\n    'uicon-lock-opened-fill': '\\ue974',\n    'uicon-lock': '\\ue97a',\n    'uicon-red-packet-fill': '\\ue690',\n    'uicon-photo-fill': '\\ue98b',\n    'uicon-photo': '\\ue98d',\n    'uicon-volume-off-fill': '\\ue659',\n    'uicon-volume-off': '\\ue644',\n    'uicon-volume-fill': '\\ue670',\n    'uicon-volume': '\\ue633',\n    'uicon-red-packet': '\\ue691',\n    'uicon-download': '\\ue63c',\n    'uicon-arrow-up-fill': '\\ue6b0',\n    'uicon-arrow-down-fill': '\\ue600',\n    'uicon-play-left-fill': '\\ue675',\n    'uicon-play-right-fill': '\\ue676',\n    'uicon-rewind-left-fill': '\\ue679',\n    'uicon-rewind-right-fill': '\\ue67a',\n    'uicon-arrow-downward': '\\ue604',\n    'uicon-arrow-leftward': '\\ue601',\n    'uicon-arrow-rightward': '\\ue603',\n    'uicon-arrow-upward': '\\ue607',\n    'uicon-arrow-down': '\\ue60d',\n    'uicon-arrow-right': '\\ue605',\n    'uicon-arrow-left': '\\ue60e',\n    'uicon-arrow-up': '\\ue606',\n    'uicon-skip-back-left': '\\ue674',\n    'uicon-skip-forward-right': '\\ue672',\n    'uicon-rewind-right': '\\ue66f',\n    'uicon-rewind-left': '\\ue671',\n    'uicon-arrow-right-double': '\\ue68d',\n    'uicon-arrow-left-double': '\\ue68c',\n    'uicon-wifi-off': '\\ue668',\n    'uicon-wifi': '\\ue667',\n    'uicon-empty-data': '\\ue62f',\n    'uicon-empty-history': '\\ue684',\n    'uicon-empty-list': '\\ue68b',\n    'uicon-empty-page': '\\ue627',\n    'uicon-empty-order': '\\ue639',\n    'uicon-man': '\\ue697',\n    'uicon-woman': '\\ue69c',\n    'uicon-man-add': '\\ue61c',\n    'uicon-man-add-fill': '\\ue64c',\n    'uicon-man-delete': '\\ue61a',\n    'uicon-man-delete-fill': '\\ue66a',\n    'uicon-zh': '\\ue70a',\n    'uicon-en': '\\ue692'\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-icon/props.js",
    "content": "export default {\n    props: {\n        // 图标类名\n        name: {\n            type: String,\n            default: uni.$u.props.icon.name\n        },\n        // 图标颜色，可接受主题色\n        color: {\n            type: String,\n            default: uni.$u.props.icon.color\n        },\n        // 字体大小，单位px\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.icon.size\n        },\n        // 是否显示粗体\n        bold: {\n            type: Boolean,\n            default: uni.$u.props.icon.bold\n        },\n        // 点击图标的时候传递事件出去的index（用于区分点击了哪一个）\n        index: {\n            type: [String, Number],\n            default: uni.$u.props.icon.index\n        },\n        // 触摸图标时的类名\n        hoverClass: {\n            type: String,\n            default: uni.$u.props.icon.hoverClass\n        },\n        // 自定义扩展前缀，方便用户扩展自己的图标库\n        customPrefix: {\n            type: String,\n            default: uni.$u.props.icon.customPrefix\n        },\n        // 图标右边或者下面的文字\n        label: {\n            type: [String, Number],\n            default: uni.$u.props.icon.label\n        },\n        // label的位置，只能右边或者下边\n        labelPos: {\n            type: String,\n            default: uni.$u.props.icon.labelPos\n        },\n        // label的大小\n        labelSize: {\n            type: [String, Number],\n            default: uni.$u.props.icon.labelSize\n        },\n        // label的颜色\n        labelColor: {\n            type: String,\n            default: uni.$u.props.icon.labelColor\n        },\n        // label与图标的距离\n        space: {\n            type: [String, Number],\n            default: uni.$u.props.icon.space\n        },\n        // 图片的mode\n        imgMode: {\n            type: String,\n            default: uni.$u.props.icon.imgMode\n        },\n        // 用于显示图片小图标时，图片的宽度\n        width: {\n            type: [String, Number],\n            default: uni.$u.props.icon.width\n        },\n        // 用于显示图片小图标时，图片的高度\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.icon.height\n        },\n        // 用于解决某些情况下，让图标垂直居中的用途\n        top: {\n            type: [String, Number],\n            default: uni.$u.props.icon.top\n        },\n        // 是否阻止事件传播\n        stop: {\n            type: Boolean,\n            default: uni.$u.props.icon.stop\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-icon/u-icon.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-icon\"\n\t    @tap=\"clickHandler\"\n\t    :class=\"['u-icon--' + labelPos]\"\n\t>\n\t\t<image\n\t\t    class=\"u-icon__img\"\n\t\t    v-if=\"isImg\"\n\t\t    :src=\"name\"\n\t\t    :mode=\"imgMode\"\n\t\t    :style=\"[imgStyle, $u.addStyle(customStyle)]\"\n\t\t></image>\n\t\t<text\n\t\t    v-else\n\t\t    class=\"u-icon__icon\"\n\t\t    :class=\"uClasses\"\n\t\t    :style=\"[iconStyle, $u.addStyle(customStyle)]\"\n\t\t    :hover-class=\"hoverClass\"\n\t\t>{{icon}}</text>\n\t\t<!-- 这里进行空字符串判断，如果仅仅是v-if=\"label\"，可能会出现传递0的时候，结果也无法显示 -->\n\t\t<text\n\t\t    v-if=\"label !== ''\" \n\t\t    class=\"u-icon__label\"\n\t\t    :style=\"{\n\t\t\tcolor: labelColor,\n\t\t\tfontSize: $u.addUnit(labelSize),\n\t\t\tmarginLeft: labelPos == 'right' ? $u.addUnit(space) : 0,\n\t\t\tmarginTop: labelPos == 'bottom' ? $u.addUnit(space) : 0,\n\t\t\tmarginRight: labelPos == 'left' ? $u.addUnit(space) : 0,\n\t\t\tmarginBottom: labelPos == 'top' ? $u.addUnit(space) : 0,\n\t\t}\"\n\t\t>{{ label }}</text>\n\t</view>\n</template>\n\n<script>\n\t// #ifdef APP-NVUE\n\t// nvue通过weex的dom模块引入字体，相关文档地址如下：\n\t// https://weex.apache.org/zh/docs/modules/dom.html#addrule\n\tconst fontUrl = 'https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf'\n\tconst domModule = weex.requireModule('dom')\n\tdomModule.addRule('fontFace', {\n\t\t'fontFamily': \"uicon-iconfont\",\n\t\t'src': `url('${fontUrl}')`\n\t})\n\t// #endif\n\n\t// 引入图标名称，已经对应的unicode\n\timport icons from './icons'\n\t\n\timport props from './props.js';;\n\n\t/**\n\t * icon 图标\n\t * @description 基于字体的图标集，包含了大多数常见场景的图标。\n\t * @tutorial https://www.uviewui.com/components/icon.html\n\t * @property {String}\t\t\tname\t\t\t图标名称，见示例图标集\n\t * @property {String}\t\t\tcolor\t\t\t图标颜色,可接受主题色 （默认 color['u-content-color'] ）\n\t * @property {String | Number}\tsize\t\t\t图标字体大小，单位px （默认 '16px' ）\n\t * @property {Boolean}\t\t\tbold\t\t\t是否显示粗体 （默认 false ）\n\t * @property {String | Number}\tindex\t\t\t点击图标的时候传递事件出去的index（用于区分点击了哪一个）\n\t * @property {String}\t\t\thoverClass\t\t图标按下去的样式类，用法同uni的view组件的hoverClass参数，详情见官网\n\t * @property {String}\t\t\tcustomPrefix\t自定义扩展前缀，方便用户扩展自己的图标库 （默认 'uicon' ）\n\t * @property {String | Number}\tlabel\t\t\t图标右侧的label文字\n\t * @property {String}\t\t\tlabelPos\t\tlabel相对于图标的位置，只能right或bottom （默认 'right' ）\n\t * @property {String | Number}\tlabelSize\t\tlabel字体大小，单位px （默认 '15px' ）\n\t * @property {String}\t\t\tlabelColor\t\t图标右侧的label文字颜色 （ 默认 color['u-content-color'] ）\n\t * @property {String | Number}\tspace\t\t\tlabel与图标的距离，单位px （默认 '3px' ）\n\t * @property {String}\t\t\timgMode\t\t\t图片的mode\n\t * @property {String | Number}\twidth\t\t\t显示图片小图标时的宽度\n\t * @property {String | Number}\theight\t\t\t显示图片小图标时的高度\n\t * @property {String | Number}\ttop\t\t\t\t图标在垂直方向上的定位 用于解决某些情况下，让图标垂直居中的用途  （默认 0 ）\n\t * @property {Boolean}\t\t\tstop\t\t\t是否阻止事件传播 （默认 false ）\n\t * @property {Object}\t\t\tcustomStyle\t\ticon的样式，对象形式\n\t * @event {Function} click 点击图标时触发\n\t * @event {Function} touchstart 事件触摸时触发\n\t * @example <u-icon name=\"photo\" color=\"#2979ff\" size=\"28\"></u-icon>\n\t */\n\texport default {\n\t\tname: 'u-icon',\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\tuClasses() {\n\t\t\t\tlet classes = []\n\t\t\t\tclasses.push(this.customPrefix + '-' + this.name)\n\t\t\t\t// // uView的自定义图标类名为u-iconfont\n\t\t\t\t// if (this.customPrefix == 'uicon') {\n\t\t\t\t// \tclasses.push('u-iconfont')\n\t\t\t\t// } else {\n\t\t\t\t// \tclasses.push(this.customPrefix)\n\t\t\t\t// }\n\t\t\t\t// 主题色，通过类配置\n\t\t\t\tif (this.color && uni.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)\n\t\t\t\t// 阿里，头条，百度小程序通过数组绑定类名时，无法直接使用[a, b, c]的形式，否则无法识别\n\t\t\t\t// 故需将其拆成一个字符串的形式，通过空格隔开各个类名\n\t\t\t\t//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU\n\t\t\t\tclasses = classes.join(' ')\n\t\t\t\t//#endif\n\t\t\t\treturn classes\n\t\t\t},\n\t\t\ticonStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\tstyle = {\n\t\t\t\t\tfontSize: uni.$u.addUnit(this.size),\n\t\t\t\t\tlineHeight: uni.$u.addUnit(this.size),\n\t\t\t\t\tfontWeight: this.bold ? 'bold' : 'normal',\n\t\t\t\t\t// 某些特殊情况需要设置一个到顶部的距离，才能更好的垂直居中\n\t\t\t\t\ttop: uni.$u.addUnit(this.top)\n\t\t\t\t}\n\t\t\t\t// 非主题色值时，才当作颜色值\n\t\t\t\tif (this.color && !uni.$u.config.type.includes(this.color)) style.color = this.color\n\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 判断传入的name属性，是否图片路径，只要带有\"/\"均认为是图片形式\n\t\t\tisImg() {\n\t\t\t\treturn this.name.indexOf('/') !== -1\n\t\t\t},\n\t\t\timgStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\t// 如果设置width和height属性，则优先使用，否则使用size属性\n\t\t\t\tstyle.width = this.width ? uni.$u.addUnit(this.width) : uni.$u.addUnit(this.size)\n\t\t\t\tstyle.height = this.height ? uni.$u.addUnit(this.height) : uni.$u.addUnit(this.size)\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 通过图标名，查找对应的图标\n\t\t\ticon() {\n\t\t\t\t// 如果内置的图标中找不到对应的图标，就直接返回name值，因为用户可能传入的是unicode代码\n\t\t\t\treturn icons['uicon-' + this.name] || this.name\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tclickHandler(e) {\n\t\t\t\tthis.$emit('click', this.index)\n\t\t\t\t// 是否阻止事件冒泡\n\t\t\t\tthis.stop && this.preventEvent(e)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t// 变量定义\n\t$u-icon-primary: $u-primary !default;\n\t$u-icon-success: $u-success !default;\n\t$u-icon-info: $u-info !default;\n\t$u-icon-warning: $u-warning !default;\n\t$u-icon-error: $u-error !default;\n\t$u-icon-label-line-height:1 !default;\n\n\t/* #ifndef APP-NVUE */\n\t// 非nvue下加载字体\n\t@font-face {\n\t\tfont-family: 'uicon-iconfont';\n\t\tsrc: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype');\n\t}\n\n\t/* #endif */\n\n\t.u-icon {\n\t\t/* #ifndef APP-NVUE */\n\t\tdisplay: flex;\n\t\t/* #endif */\n\t\talign-items: center;\n\n\t\t&--left {\n\t\t\tflex-direction: row-reverse;\n\t\t\talign-items: center;\n\t\t}\n\n\t\t&--right {\n\t\t\tflex-direction: row;\n\t\t\talign-items: center;\n\t\t}\n\n\t\t&--top {\n\t\t\tflex-direction: column-reverse;\n\t\t\tjustify-content: center;\n\t\t}\n\n\t\t&--bottom {\n\t\t\tflex-direction: column;\n\t\t\tjustify-content: center;\n\t\t}\n\n\t\t&__icon {\n\t\t\tfont-family: uicon-iconfont;\n\t\t\tposition: relative;\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\n\t\t\t&--primary {\n\t\t\t\tcolor: $u-icon-primary;\n\t\t\t}\n\n\t\t\t&--success {\n\t\t\t\tcolor: $u-icon-success;\n\t\t\t}\n\n\t\t\t&--error {\n\t\t\t\tcolor: $u-icon-error;\n\t\t\t}\n\n\t\t\t&--warning {\n\t\t\t\tcolor: $u-icon-warning;\n\t\t\t}\n\n\t\t\t&--info {\n\t\t\t\tcolor: $u-icon-info;\n\t\t\t}\n\t\t}\n\n\t\t&__img {\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\theight: auto;\n\t\t\twill-change: transform;\n\t\t\t/* #endif */\n\t\t}\n\n\t\t&__label {\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tline-height: $u-icon-label-line-height;\n\t\t\t/* #endif */\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-image/props.js",
    "content": "export default {\n    props: {\n        // 图片地址\n        src: {\n            type: String,\n            default: uni.$u.props.image.src\n        },\n        // 裁剪模式\n        mode: {\n            type: String,\n            default: uni.$u.props.image.mode\n        },\n        // 宽度，单位任意\n        width: {\n            type: [String, Number],\n            default: uni.$u.props.image.width\n        },\n        // 高度，单位任意\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.image.height\n        },\n        // 图片形状，circle-圆形，square-方形\n        shape: {\n            type: String,\n            default: uni.$u.props.image.shape\n        },\n        // 圆角，单位任意\n        radius: {\n            type: [String, Number],\n            default: uni.$u.props.image.radius\n        },\n        // 是否懒加载，微信小程序、App、百度小程序、字节跳动小程序\n        lazyLoad: {\n            type: Boolean,\n            default: uni.$u.props.image.lazyLoad\n        },\n        // 开启长按图片显示识别微信小程序码菜单\n        showMenuByLongpress: {\n            type: Boolean,\n            default: uni.$u.props.image.showMenuByLongpress\n        },\n        // 加载中的图标，或者小图片\n        loadingIcon: {\n            type: String,\n            default: uni.$u.props.image.loadingIcon\n        },\n        // 加载失败的图标，或者小图片\n        errorIcon: {\n            type: String,\n            default: uni.$u.props.image.errorIcon\n        },\n        // 是否显示加载中的图标或者自定义的slot\n        showLoading: {\n            type: Boolean,\n            default: uni.$u.props.image.showLoading\n        },\n        // 是否显示加载错误的图标或者自定义的slot\n        showError: {\n            type: Boolean,\n            default: uni.$u.props.image.showError\n        },\n        // 是否需要淡入效果\n        fade: {\n            type: Boolean,\n            default: uni.$u.props.image.fade\n        },\n        // 只支持网络资源，只对微信小程序有效\n        webp: {\n            type: Boolean,\n            default: uni.$u.props.image.webp\n        },\n        // 过渡时间，单位ms\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.image.duration\n        },\n        // 背景颜色，用于深色页面加载图片时，为了和背景色融合\n        bgColor: {\n            type: String,\n            default: uni.$u.props.image.bgColor\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-image/u-image.vue",
    "content": "<template>\n\t<u-transition\n\t\tmode=\"fade\"\n\t\t:show=\"show\"\n\t\t:duration=\"fade ? 1000 : 0\"\n\t>\n\t\t<view\n\t\t\tclass=\"u-image\"\n\t\t\t@tap=\"onClick\"\n\t\t\t:style=\"[wrapStyle, backgroundStyle]\"\n\t\t>\n\t\t\t<image\n\t\t\t\tv-if=\"!isError\"\n\t\t\t\t:src=\"src\"\n\t\t\t\t:mode=\"mode\"\n\t\t\t\t@error=\"onErrorHandler\"\n\t\t\t\t@load=\"onLoadHandler\"\n\t\t\t\t:show-menu-by-longpress=\"showMenuByLongpress\"\n\t\t\t\t:lazy-load=\"lazyLoad\"\n\t\t\t\tclass=\"u-image__image\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tborderRadius: shape == 'circle' ? '10000px' : $u.addUnit(radius),\n\t\t\t\t\twidth: $u.addUnit(width),\n\t\t\t\t\theight: $u.addUnit(height)\n\t\t\t\t}\"\n\t\t\t></image>\n\t\t\t<view\n\t\t\t\tv-if=\"showLoading && loading\"\n\t\t\t\tclass=\"u-image__loading\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tborderRadius: shape == 'circle' ? '50%' : $u.addUnit(radius),\n\t\t\t\t\tbackgroundColor: this.bgColor,\n\t\t\t\t\twidth: $u.addUnit(width),\n\t\t\t\t\theight: $u.addUnit(height)\n\t\t\t\t}\"\n\t\t\t>\n\t\t\t\t<slot name=\"loading\">\n\t\t\t\t\t<u-icon\n\t\t\t\t\t\t:name=\"loadingIcon\"\n\t\t\t\t\t\t:width=\"width\"\n\t\t\t\t\t\t:height=\"height\"\n\t\t\t\t\t></u-icon>\n\t\t\t\t</slot>\n\t\t\t</view>\n\t\t\t<view\n\t\t\t\tv-if=\"showError && isError && !loading\"\n\t\t\t\tclass=\"u-image__error\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tborderRadius: shape == 'circle' ? '50%' : $u.addUnit(radius),\n\t\t\t\t\twidth: $u.addUnit(width),\n\t\t\t\t\theight: $u.addUnit(height)\n\t\t\t\t}\"\n\t\t\t>\n\t\t\t\t<slot name=\"error\">\n\t\t\t\t\t<u-icon\n\t\t\t\t\t\t:name=\"errorIcon\"\n\t\t\t\t\t\t:width=\"width\"\n\t\t\t\t\t\t:height=\"height\"\n\t\t\t\t\t></u-icon>\n\t\t\t\t</slot>\n\t\t\t</view>\n\t\t</view>\n\t</u-transition>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Image 图片\n\t * @description 此组件为uni-app的image组件的加强版，在继承了原有功能外，还支持淡入动画、加载中、加载失败提示、圆角值和形状等。\n\t * @tutorial https://uviewui.com/components/image.html\n\t * @property {String}\t\t\tsrc \t\t\t\t图片地址\n\t * @property {String}\t\t\tmode \t\t\t\t裁剪模式，见官网说明 （默认 'aspectFill' ）\n\t * @property {String | Number}\twidth \t\t\t\t宽度，单位任意，如果为数值，则为px单位 （默认 '300' ）\n\t * @property {String | Number}\theight \t\t\t\t高度，单位任意，如果为数值，则为px单位 （默认 '225' ）\n\t * @property {String}\t\t\tshape \t\t\t\t图片形状，circle-圆形，square-方形 （默认 'square' ）\n\t * @property {String | Number}\tradius\t\t \t\t圆角值，单位任意，如果为数值，则为px单位 （默认 0 ）\n\t * @property {Boolean}\t\t\tlazyLoad\t\t\t是否懒加载，仅微信小程序、App、百度小程序、字节跳动小程序有效 （默认 true ）\n\t * @property {Boolean}\t\t\tshowMenuByLongpress\t是否开启长按图片显示识别小程序码菜单，仅微信小程序有效 （默认 true ）\n\t * @property {String}\t\t\tloadingIcon \t\t加载中的图标，或者小图片 （默认 'photo' ）\n\t * @property {String}\t\t\terrorIcon \t\t\t加载失败的图标，或者小图片 （默认 'error-circle' ）\n\t * @property {Boolean}\t\t\tshowLoading \t\t是否显示加载中的图标或者自定义的slot （默认 true ）\n\t * @property {Boolean}\t\t\tshowError \t\t\t是否显示加载错误的图标或者自定义的slot （默认 true ）\n\t * @property {Boolean}\t\t\tfade \t\t\t\t是否需要淡入效果 （默认 true ）\n\t * @property {Boolean}\t\t\twebp \t\t\t\t只支持网络资源，只对微信小程序有效 （默认 false ）\n\t * @property {String | Number}\tduration \t\t\t搭配fade参数的过渡时间，单位ms （默认 500 ）\n\t * @property {String}\t\t\tbgColor \t\t\t背景颜色，用于深色页面加载图片时，为了和背景色融合  (默认 '#f3f4f6' )\n\t * @property {Object}\t\t\tcustomStyle  \t\t定义需要用到的外部样式\n\t * @event {Function}\tclick\t点击图片时触发\n\t * @event {Function}\terror\t图片加载失败时触发\n\t * @event {Function} load 图片加载成功时触发\n\t * @example <u-image width=\"100%\" height=\"300px\" :src=\"src\"></u-image>\n\t */\n\texport default {\n\t\tname: 'u-image',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 图片是否加载错误，如果是，则显示错误占位图\n\t\t\t\tisError: false,\n\t\t\t\t// 初始化组件时，默认为加载中状态\n\t\t\t\tloading: true,\n\t\t\t\t// 不透明度，为了实现淡入淡出的效果\n\t\t\t\topacity: 1,\n\t\t\t\t// 过渡时间，因为props的值无法修改，故需要一个中间值\n\t\t\t\tdurationTime: this.duration,\n\t\t\t\t// 图片加载完成时，去掉背景颜色，因为如果是png图片，就会显示灰色的背景\n\t\t\t\tbackgroundStyle: {},\n\t\t\t\t// 用于fade模式的控制组件显示与否\n\t\t\t\tshow: false\n\t\t\t};\n\t\t},\n\t\twatch: {\n\t\t\tsrc: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(n) {\n\t\t\t\t\tif (!n) {\n\t\t\t\t\t\t// 如果传入null或者''，或者false，或者undefined，标记为错误状态\n\t\t\t\t\t\tthis.isError = true\n\t\t\t\t\t\t\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.isError = false;\n\t\t\t\t\t\tthis.loading = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\twrapStyle() {\n\t\t\t\tlet style = {};\n\t\t\t\t// 通过调用addUnit()方法，如果有单位，如百分比，px单位等，直接返回，如果是纯粹的数值，则加上rpx单位\n\t\t\t\tstyle.width = this.$u.addUnit(this.width);\n\t\t\t\tstyle.height = this.$u.addUnit(this.height);\n\t\t\t\t// 如果是显示圆形，设置一个很多的半径值即可\n\t\t\t\tstyle.borderRadius = this.shape == 'circle' ? '10000px' : uni.$u.addUnit(this.radius)\n\t\t\t\t// 如果设置圆角，必须要有hidden，否则可能圆角无效\n\t\t\t\tstyle.overflow = this.borderRadius > 0 ? 'hidden' : 'visible'\n\t\t\t\t// if (this.fade) {\n\t\t\t\t// \tstyle.opacity = this.opacity\n\t\t\t\t// \t// nvue下，这几个属性必须要分开写\n\t\t\t\t// \tstyle.transitionDuration = `${this.durationTime}ms`\n\t\t\t\t// \tstyle.transitionTimingFunction = 'ease-in-out'\n\t\t\t\t// \tstyle.transitionProperty = 'opacity'\n\t\t\t\t// }\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));\n\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.show = true\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击图片\n\t\t\tonClick() {\n\t\t\t\tthis.$emit('click')\n\t\t\t},\n\t\t\t// 图片加载失败\n\t\t\tonErrorHandler(err) {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.isError = true\n\t\t\t\tthis.$emit('error', err)\n\t\t\t},\n\t\t\t// 图片加载完成，标记loading结束\n\t\t\tonLoadHandler(event) {\n\t\t\t\tthis.loading = false\n\t\t\t\tthis.isError = false\n\t\t\t\tthis.$emit('load', event)\n\t\t\t\tthis.removeBgColor()\n\t\t\t\t// 如果不需要动画效果，就不执行下方代码，同时移除加载时的背景颜色\n\t\t\t\t// 否则无需fade效果时，png图片依然能看到下方的背景色\n\t\t\t\t// if (!this.fade) return this.removeBgColor();\n\t\t\t\t// // 原来opacity为1(不透明，是为了显示占位图)，改成0(透明，意味着该元素显示的是背景颜色，默认的灰色)，再改成1，是为了获得过渡效果\n\t\t\t\t// this.opacity = 0;\n\t\t\t\t// // 这里设置为0，是为了图片展示到背景全透明这个过程时间为0，延时之后延时之后重新设置为duration，是为了获得背景透明(灰色)\n\t\t\t\t// // 到图片展示的过程中的淡入效果\n\t\t\t\t// this.durationTime = 0;\n\t\t\t\t// // 延时50ms，否则在浏览器H5，过渡效果无效\n\t\t\t\t// setTimeout(() => {\n\t\t\t\t// \tthis.durationTime = this.duration;\n\t\t\t\t// \tthis.opacity = 1;\n\t\t\t\t// \tsetTimeout(() => {\n\t\t\t\t// \t\tthis.removeBgColor();\n\t\t\t\t// \t}, this.durationTime);\n\t\t\t\t// }, 50);\n\t\t\t},\n\t\t\t// 移除图片的背景色\n\t\t\tremoveBgColor() {\n\t\t\t\t// 淡入动画过渡完成后，将背景设置为透明色，否则png图片会看到灰色的背景\n\t\t\t\tthis.backgroundStyle = {\n\t\t\t\t\tbackgroundColor: 'transparent'\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import '../../libs/css/components.scss';\n\n\t$u-image-error-top:0px !default;\n\t$u-image-error-left:0px !default;\n\t$u-image-error-width:100% !default;\n\t$u-image-error-hight:100% !default;\n\t$u-image-error-background-color:$u-bg-color !default;\n\t$u-image-error-color:$u-tips-color !default;\n\t$u-image-error-font-size: 46rpx !default;\n\n\t.u-image {\n\t\tposition: relative;\n\t\ttransition: opacity 0.5s ease-in-out;\n\n\t\t&__image {\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t}\n\n\t\t&__loading,\n\t\t&__error {\n\t\t\tposition: absolute;\n\t\t\ttop: $u-image-error-top;\n\t\t\tleft: $u-image-error-left;\n\t\t\twidth: $u-image-error-width;\n\t\t\theight: $u-image-error-hight;\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tbackground-color: $u-image-error-background-color;\n\t\t\tcolor: $u-image-error-color;\n\t\t\tfont-size: $u-image-error-font-size;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-index-anchor/props.js",
    "content": "export default {\n    props: {\n        // 列表锚点文本内容\n        text: {\n            type: [String, Number],\n            default: uni.$u.props.indexAnchor.text\n        },\n        // 列表锚点文字颜色\n        color: {\n            type: String,\n            default: uni.$u.props.indexAnchor.color\n        },\n        // 列表锚点文字大小，单位默认px\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.indexAnchor.size\n        },\n        // 列表锚点背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.indexAnchor.bgColor\n        },\n        // 列表锚点高度，单位默认px\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.indexAnchor.height\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-index-anchor/u-index-anchor.vue",
    "content": "<template>\n\t<!-- #ifdef APP-NVUE -->\n\t<header>\n\t<!-- #endif -->\n\t<view\n\t    class=\"u-index-anchor u-border-bottom\"\n\t\t:ref=\"`u-index-anchor-${text}`\"\n\t    :style=\"{\n\t\t\theight: $u.addUnit(height),\n\t\t\tbackgroundColor: bgColor\n\t\t}\"\n\t>\n\t\t<text\n\t\t    class=\"u-index-anchor__text\"\n\t\t    :style=\"{\n\t\t\t\tfontSize: $u.addUnit(size),\n\t\t\t\tcolor: color\n\t\t\t}\"\n\t\t>{{ text }}</text>\n\t</view>\n\t<!-- #ifdef APP-NVUE -->\n\t</header>\n\t<!-- #endif -->\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * IndexAnchor 列表锚点\n\t * @description \n\t * @tutorial https://uviewui.com/components/indexList.html\n\t * @property {String | Number}\ttext\t列表锚点文本内容\n\t * @property {String}\t\t\tcolor\t列表锚点文字颜色 ( 默认 '#606266' )\n\t * @property {String | Number}\tsize\t列表锚点文字大小，单位默认px ( 默认 14 )\n\t * @property {String}\t\t\tbgColor\t列表锚点背景颜色 ( 默认 '#dedede' )\n\t * @property {String | Number}\theight\t列表锚点高度，单位默认px ( 默认 32 )\n\t * @example <u-index-anchor :text=\"indexList[index]\"></u-index-anchor>\n\t */\n\texport default {\n\t\tname: 'u-index-anchor',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 此处会活动父组件实例，并赋值给实例的parent属性\n\t\t\t\tconst indexList = uni.$u.$parent.call(this, 'u-index-list')\n\t\t\t\tif (!indexList) { \n\t\t\t\t\treturn uni.$u.error('u-index-anchor必须要搭配u-index-list组件使用')\n\t\t\t\t}\n\t\t\t\t// 将当前实例放入到u-index-list中\n\t\t\t\tindexList.anchors.push(this)\n\t\t\t\tconst indexListItem = uni.$u.$parent.call(this, 'u-index-item')\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t// 只有在非nvue下，u-index-anchor才是嵌套在u-index-item中的\n\t\t\t\tif (!indexListItem) {\n\t\t\t\t\treturn uni.$u.error('u-index-anchor必须要搭配u-index-item组件使用')\n\t\t\t\t}\n\t\t\t\t// 设置u-index-item的id为anchor的text标识符，因为非nvue下滚动列表需要依赖scroll-view滚动到元素的特性\n\t\t\t\tindexListItem.id = this.text.charCodeAt(0)\n\t\t\t\t// #endif\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-index-anchor {\n\t\tposition: sticky;\n\t\ttop: 0;\n\t\t@include flex;\n\t\talign-items: center;\n\t\tpadding-left: 15px;\n\t\tz-index: 1;\n\n\t\t&__text {\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-index-item/props.js",
    "content": "export default {\n    props: {\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-index-item/u-index-item.vue",
    "content": "<template>\n\t<!-- #ifdef APP-NVUE -->\n\t<cell ref=\"u-index-item\">\n\t\t<!-- #endif -->\n\t\t<view\n\t\t\tclass=\"u-index-item\"\n\t\t\t:id=\"`u-index-item-${id}`\"\n\t\t\t:class=\"[`u-index-item-${id}`]\"\n\t\t>\n\t\t\t<slot />\n\t\t</view>\n\t\t<!-- #ifdef APP-NVUE -->\n\t</cell>\n\t<!-- #endif -->\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\t// 由于weex为阿里的KPI业绩考核的产物，所以不支持百分比单位，这里需要通过dom查询组件的宽度\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * IndexItem \n\t * @description \n\t * @tutorial https://uviewui.com/components/indexList.html\n\t * @property {String}\n\t * @event {Function}\n\t * @example\n\t */\n\texport default {\n\t\tname: 'u-index-item',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 本组件到滚动条顶部的距离\n\t\t\t\ttop: 0,\n\t\t\t\theight: 0,\n\t\t\t\tid: ''\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\t// 子组件u-index-anchor的实例\n\t\t\tthis.anchor = {}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 此处会活动父组件实例，并赋值给实例的parent属性\n\t\t\t\tthis.getParentData('u-index-list')\n\t\t\t\tif (!this.parent) {\n\t\t\t\t\treturn uni.$u.error('u-index-item必须要搭配u-index-list组件使用')\n\t\t\t\t}\n\t\t\t\tuni.$u.sleep().then(() =>{\n\t\t\t\t\tthis.getIndexItemRect().then(size => {\n\t\t\t\t\t\t// 由于对象的引用特性，此处会同时生效到父组件的children数组的本实例的top属性中，供父组件判断读取\n\t\t\t\t\t\tthis.top = Math.ceil(size.top)\n\t\t\t\t\t\tthis.height = Math.ceil(size.height)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t\tgetIndexItemRect() {\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\tthis.$uGetRect('.u-index-item').then(size => {\n\t\t\t\t\t\tresolve(size)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\tconst ref = this.$refs['u-index-item']\n\t\t\t\t\tdom.getComponentRect(ref, res => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\t\t\t\t}) \n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-index-list/props.js",
    "content": "export default {\n    props: {\n        // 右边锚点非激活的颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.indexList.inactiveColor\n        },\n        // 右边锚点激活的颜色\n        activeColor: {\n            type: String,\n            default: uni.$u.props.indexList.activeColor\n        },\n        // 索引字符列表，数组形式\n        indexList: {\n            type: Array,\n            default: uni.$u.props.indexList.indexList\n        },\n        // 是否开启锚点自动吸顶\n        sticky: {\n            type: Boolean,\n            default: uni.$u.props.indexList.sticky\n        },\n        // 自定义导航栏的高度\n        customNavHeight: {\n            type: [String, Number],\n            default: uni.$u.props.indexList.customNavHeight\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-index-list/u-index-list.vue",
    "content": "<template>\n\t<view class=\"u-index-list\">\n\t\t<!-- #ifdef APP-NVUE -->\n\t\t<list\n\t\t\t:scrollTop=\"scrollTop\"\n\t\t\tenable-back-to-top\n\t\t\t:offset-accuracy=\"1\"\n\t\t\t:style=\"{\n\t\t\t\tmaxHeight: $u.addUnit(scrollViewHeight)\n\t\t\t}\"\n\t\t\t@scroll=\"scrollHandler\"\n\t\t\tref=\"uList\"\n\t\t>\n\t\t\t<cell\n\t\t\t\tv-if=\"$slots.header\"\n\t\t\t\tref=\"header\"\n\t\t\t>\n\t\t\t\t<slot name=\"header\" />\n\t\t\t</cell>\n\t\t\t<slot />\n\t\t\t<cell v-if=\"$slots.footer\">\n\t\t\t\t<slot name=\"footer\" />\n\t\t\t</cell>\n\t\t</list>\n\t\t<!-- #endif -->\n\t\t<!-- #ifndef APP-NVUE -->\n\t\t<scroll-view\n\t\t\t:scrollTop=\"scrollTop\"\n\t\t\t:scrollIntoView=\"scrollIntoView\"\n\t\t\t:offset-accuracy=\"1\"\n\t\t\t:style=\"{\n\t\t\t\tmaxHeight: $u.addUnit(scrollViewHeight)\n\t\t\t}\"\n\t\t\tscroll-y\n\t\t\t@scroll=\"scrollHandler\"\n\t\t\tref=\"uList\"\n\t\t>\n\t\t\t<view v-if=\"$slots.header\">\n\t\t\t\t<slot name=\"header\" />\n\t\t\t</view>\n\t\t\t<slot />\n\t\t\t<view v-if=\"$slots.footer\">\n\t\t\t\t<slot name=\"footer\" />\n\t\t\t</view>\n\t\t</scroll-view>\n\t\t<!-- #endif -->\n\t\t<view\n\t\t\tclass=\"u-index-list__letter\"\n\t\t\tref=\"u-index-list__letter\"\n\t\t\t:style=\"{ top: $u.addUnit(letterInfo.top || 100) }\"\n\t\t\t@touchstart=\"touchStart\"\n\t\t\t@touchmove.stop.prevent=\"touchMove\"\n\t\t\t@touchend.stop.prevent=\"touchEnd\"\n\t\t\t@touchcancel.stop.prevent=\"touchEnd\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-index-list__letter__item\"\n\t\t\t\tv-for=\"(item, index) in uIndexList\"\n\t\t\t\t:key=\"index\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tbackgroundColor: activeIndex === index ? activeColor : 'transparent'\n\t\t\t\t}\"\n\t\t\t>\n\t\t\t\t<text\n\t\t\t\t\tclass=\"u-index-list__letter__item__index\"\n\t\t\t\t\t:style=\"{color: activeIndex === index ? '#fff' : inactiveColor}\"\n\t\t\t\t>{{ item }}</text>\n\t\t\t</view>\n\t\t</view>\n\t\t<u-transition\n\t\t\tmode=\"fade\"\n\t\t\t:show=\"touching\"\n\t\t\t:customStyle=\"{\n\t\t\t\tposition: 'fixed',\n\t\t\t\tright: '50px',\n\t\t\t\ttop: $u.addUnit(indicatorTop),\n\t\t\t\tzIndex: 2\n\t\t\t}\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-index-list__indicator\"\n\t\t\t\t:class=\"['u-index-list__indicator--show']\"\n\t\t\t\t:style=\"{\n\t\t\t\t\theight: $u.addUnit(indicatorHeight),\n\t\t\t\t\twidth: $u.addUnit(indicatorHeight)\n\t\t\t\t}\"\n\t\t\t>\n\t\t\t\t<text class=\"u-index-list__indicator__text\">{{ uIndexList[activeIndex] }}</text>\n\t\t\t</view>\n\t\t</u-transition>\n\t</view>\n</template>\n\n<script>\n\tconst indexList = () => {\n\t\tconst indexList = [];\n\t\tconst charCodeOfA = 'A'.charCodeAt(0);\n\t\tfor (let i = 0; i < 26; i++) {\n\t\t\tindexList.push(String.fromCharCode(charCodeOfA + i));\n\t\t}\n\t\treturn indexList;\n\t}\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\t// 由于weex为阿里的KPI业绩考核的产物，所以不支持百分比单位，这里需要通过dom查询组件的宽度\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * IndexList 索引列表\n\t * @description  通过折叠面板收纳内容区域\n\t * @tutorial https://uviewui.com/components/indexList.html\n\t * @property {String}\t\t\tinactiveColor\t右边锚点非激活的颜色 ( 默认 '#606266' )\n\t * @property {String}\t\t\tactiveColor\t\t右边锚点激活的颜色 ( 默认 '#5677fc' )\n\t * @property {Array}\t\t\tindexList\t\t索引字符列表，数组形式\n\t * @property {Boolean}\t\t\tsticky\t\t\t是否开启锚点自动吸顶 ( 默认 true )\n\t * @property {String | Number}\tcustomNavHeight\t自定义导航栏的高度 ( 默认 0 )\n\t * */ \n\texport default {\n\t\tname: 'u-index-list',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\t// #ifdef MP-WEIXIN\n\t\t// 将自定义节点设置成虚拟的，更加接近Vue组件的表现，能更好的使用flex属性\n\t\toptions: {\n\t\t\tvirtualHost: true\n\t\t},\n\t\t// #endif\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 当前正在被选中的字母索引\n\t\t\t\tactiveIndex: -1,\n\t\t\t\ttouchmoveIndex: 1,\n\t\t\t\t// 索引字母的信息\n\t\t\t\tletterInfo: {\n\t\t\t\t\theight: 0,\n\t\t\t\t\titemHeight: 0,\n\t\t\t\t\ttop: 0\n\t\t\t\t},\n\t\t\t\t// 设置字母指示器的高度，后面为了让指示器跟随字母，并将尖角部分指向字母的中部，需要依赖此值\n\t\t\t\tindicatorHeight: 50,\n\t\t\t\t// 字母放大指示器的top值，为了让其指向当前激活的字母\n\t\t\t\t// indicatorTop: 0\n\t\t\t\t// 当前是否正在被触摸状态\n\t\t\t\ttouching: false,\n\t\t\t\t// 滚动条顶部top值\n\t\t\t\tscrollTop: 0,\n\t\t\t\t// scroll-view的高度\n\t\t\t\tscrollViewHeight: 0,\n\t\t\t\t// 系统信息\n\t\t\t\tsys: uni.$u.sys(),\n\t\t\t\tscrolling: false,\n\t\t\t\tscrollIntoView: '',\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 如果有传入外部的indexList锚点数组则使用，否则使用内部生成A-Z字母\n\t\t\tuIndexList() {\n\t\t\t\treturn this.indexList.length ? this.indexList : indexList()\n\t\t\t},\n\t\t\t// 字母放大指示器的top值，为了让其指向当前激活的字母\n\t\t\tindicatorTop() {\n\t\t\t\tconst {\n\t\t\t\t\ttop,\n\t\t\t\t\titemHeight\n\t\t\t\t} = this.letterInfo\n\t\t\t\treturn Math.floor(top + itemHeight * this.activeIndex + itemHeight / 2 - this.indicatorHeight / 2)\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t// 监听字母索引的变化，重新设置尺寸\n\t\t\tuIndexList: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler() {\n\t\t\t\t\tuni.$u.sleep().then(() => {\n\t\t\t\t\t\tthis.setIndexListLetterInfo()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.children = []\n\t\t\tthis.anchors = []\n\t\t\tthis.init()\n\t\t},\n\t\tmounted() {\n\t\t\tthis.setIndexListLetterInfo()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 设置列表的高度为整个屏幕的高度\n\t\t\t\t//减去this.customNavHeight，并将this.scrollViewHeight设置为maxHeight\n\t\t\t\t//解决当u-index-list组件放在tabbar页面时,scroll-view内容较少时，还能滚动\n\t\t\t\tthis.scrollViewHeight = this.sys.windowHeight - this.customNavHeight\n\t\t\t},\n\t\t\t// 索引列表被触摸\n\t\t\ttouchStart(e) {\n\t\t\t\t// 获取触摸点信息\n\t\t\t\tconst touchStart = e.changedTouches[0]\n\t\t\t\tif (!touchStart) return\n\t\t\t\tthis.touching = true\n\t\t\t\tconst {\n\t\t\t\t\tpageY\n\t\t\t\t} = touchStart\n\t\t\t\t// 根据当前触摸点的坐标，获取当前触摸的为第几个字母\n\t\t\t\tconst currentIndex = this.getIndexListLetter(pageY)\n\t\t\t\tthis.setValueForTouch(currentIndex)\n\t\t\t},\n\t\t\t// 索引字母列表被触摸滑动中\n\t\t\ttouchMove(e) {\n\t\t\t\t// 获取触摸点信息\n\t\t\t\tlet touchMove = e.changedTouches[0]\n\t\t\t\tif (!touchMove) return;\n\n\t\t\t\t// 滑动结束后迅速开始第二次滑动时候 touching 为 false 造成不显示 indicator 问题\n\t\t\t\tif (!this.touching) {\n\t\t\t\t\tthis.touching = true\n\t\t\t\t}\n\t\t\t\tconst {\n\t\t\t\t\tpageY\n\t\t\t\t} = touchMove\n\t\t\t\tconst currentIndex = this.getIndexListLetter(pageY)\n\t\t\t\tthis.setValueForTouch(currentIndex)\n\t\t\t},\n\t\t\t// 触摸结束\n\t\t\ttouchEnd(e) {\n\t\t\t\t// 延时一定时间后再隐藏指示器，为了让用户看的更直观，同时也是为了消除快速切换u-transition的show带来的影响\n\t\t\t\tuni.$u.sleep(300).then(() => {\n\t\t\t\t\tthis.touching = false\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 获取索引列表的尺寸以及单个字符的尺寸信息\n\t\t\tgetIndexListLetterRect() {\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t// 延时一定时间，以获取dom尺寸\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\tthis.$uGetRect('.u-index-list__letter').then(size => {\n\t\t\t\t\t\tresolve(size)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\tconst ref = this.$refs['u-index-list__letter']\n\t\t\t\t\tdom.getComponentRect(ref, res => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 设置indexList索引的尺寸信息\n\t\t\tsetIndexListLetterInfo() {\n\t\t\t\tthis.getIndexListLetterRect().then(size => {\n\t\t\t\t\tconst {\n\t\t\t\t\t\theight\n\t\t\t\t\t} = size\n\t\t\t\t\tconst sys = uni.$u.sys()\n\t\t\t\t\tconst windowHeight = sys.windowHeight\n\t\t\t\t\tlet customNavHeight = 0\n\t\t\t\t\t// 消除各端导航栏非原生和原生导致的差异，让索引列表字母对屏幕垂直居中\n\t\t\t\t\tif (this.customNavHeight == 0) {\n\t\t\t\t\t\t// #ifdef H5\n\t\t\t\t\t\tcustomNavHeight = sys.windowTop\n\t\t\t\t\t\t// #endif\n\t\t\t\t\t\t// #ifndef H5\n\t\t\t\t\t\t// 在非H5中，为原生导航栏，其高度不算在windowHeight内，这里设置为负值，后面相加时变成减去其高度的一半\n\t\t\t\t\t\tcustomNavHeight = -(sys.statusBarHeight + 44)\n\t\t\t\t\t\t// #endif\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcustomNavHeight = uni.$u.getPx(this.customNavHeight)\n\t\t\t\t\t}\n\t\t\t\t\tthis.letterInfo = {\n\t\t\t\t\t\theight,\n\t\t\t\t\t\t// 为了让字母列表对屏幕绝对居中，让其对导航栏进行修正，也即往上偏移导航栏的一半高度\n\t\t\t\t\t\ttop: (windowHeight - height) / 2 + customNavHeight / 2,\n\t\t\t\t\t\titemHeight: Math.floor(height / this.uIndexList.length)\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 获取当前被触摸的索引字母\n\t\t\tgetIndexListLetter(pageY) {\n\t\t\t\tconst {\n\t\t\t\t\ttop,\n\t\t\t\t\theight,\n\t\t\t\t\titemHeight\n\t\t\t\t} = this.letterInfo\n\t\t\t\t// 对H5的pageY进行修正，这是由于uni-app自作多情在H5中将触摸点的坐标跟H5的导航栏结合导致的问题\n\t\t\t\t// #ifdef H5\n\t\t\t\tpageY += uni.$u.sys().windowTop\n\t\t\t\t// #endif\n\t\t\t\t// 对第一和最后一个字母做边界处理，因为用户可能在字母列表上触摸到两端的尽头后依然继续滑动\n\t\t\t\tif (pageY < top) {\n\t\t\t\t\treturn 0\n\t\t\t\t} else if (pageY >= top + height) {\n\t\t\t\t\t// 如果超出了，取最后一个字母\n\t\t\t\t\treturn this.uIndexList.length - 1\n\t\t\t\t} else {\n\t\t\t\t\t// 将触摸点的Y轴偏移值，减去索引字母的top值，除以每个字母的高度，即可得到当前触摸点落在哪个字母上\n\t\t\t\t\treturn Math.floor((pageY - top) / itemHeight);\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 设置各项由触摸而导致变化的值\n\t\t\tsetValueForTouch(currentIndex) {\n\t\t\t\t// 如果偏移量太小，前后得出的会是同一个索引字母，为了防抖，进行返回\n\t\t\t\tif (currentIndex === this.activeIndex) return\n\t\t\t\tthis.activeIndex = currentIndex\n\t\t\t\t// #ifndef APP-NVUE || MP-WEIXIN\n\t\t\t\t// 在非nvue中，由于anchor和item都在u-index-item中，所以需要对index-item进行偏移\n\t\t\t\tthis.scrollIntoView = `u-index-item-${this.uIndexList[currentIndex].charCodeAt(0)}`\n\t\t\t\t// #endif\n\t\t\t\t// #ifdef MP-WEIXIN\n\t\t\t\t// 微信小程序下，scroll-view的scroll-into-view属性无法对slot中的内容的id生效，只能通过设置scrollTop的形式去移动滚动条\n\t\t\t\tthis.scrollTop = this.children[currentIndex].top\n\t\t\t\t// #endif\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// 在nvue中，由于cell和header为同级元素，所以实际是需要对header(anchor)进行偏移\n\t\t\t\tconst anchor = `u-index-anchor-${this.uIndexList[currentIndex]}`\n\t\t\t\tdom.scrollToElement(this.anchors[currentIndex].$refs[anchor], {\n\t\t\t\t\toffset: 0,\n\t\t\t\t\tanimated: false\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\tgetHeaderRect() {\n\t\t\t\t// 获取header slot的高度，因为list组件中获取元素的尺寸是没有top值的\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tdom.getComponentRect(this.$refs.header, res => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t\t// scroll-view的滚动事件\n\t\t\tasync scrollHandler(e) {\n\t\t\t\tif (this.touching || this.scrolling) return\n\t\t\t\t// 每过一定时间取样一次，减少资源损耗以及可能带来的卡顿\n\t\t\t\tthis.scrolling = true\n\t\t\t\tuni.$u.sleep(10).then(() => {\n\t\t\t\t\tthis.scrolling = false\n\t\t\t\t})\n\t\t\t\tlet scrollTop = 0\n\t\t\t\tconst len = this.children.length\n\t\t\t\tlet children = this.children\n\t\t\t\tconst anchors = this.anchors\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// nvue下获取的滚动条偏移为负数，需要转为正数\n\t\t\t\tscrollTop = Math.abs(e.contentOffset.y)\n\t\t\t\t// 获取header slot的尺寸信息\n\t\t\t\tconst header = await this.getHeaderRect()\n\t\t\t\t// item的top值，在nvue下，模拟出的anchor的top，类似非nvue下的index-item的top\n\t\t\t\tlet top = header.height\n\t\t\t\t// 由于list组件无法获取cell的top值，这里通过header slot和各个item之间的height，模拟出类似非nvue下的位置信息\n\t\t\t\tchildren = this.children.map((item, index) => {\n\t\t\t\t\tconst child = {\n\t\t\t\t\t\theight: item.height,\n\t\t\t\t\t\ttop\n\t\t\t\t\t}\n\t\t\t\t\t// 进行累加，给下一个item提供计算依据\n\t\t\t\t\ttop += item.height + anchors[index].height\n\t\t\t\t\treturn child\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t// 非nvue通过detail获取滚动条位移\n\t\t\t\tscrollTop = e.detail.scrollTop\n\t\t\t\t// #endif\n\t\t\t\tfor (let i = 0; i < len; i++) {\n\t\t\t\t\tconst item = children[i],\n\t\t\t\t\t\tnextItem = children[i + 1]\n\t\t\t\t\t// 如果滚动条高度小于第一个item的top值，此时无需设置任意字母为高亮\n\t\t\t\t\tif (scrollTop <= children[0].top || scrollTop >= children[len - 1].top + children[len -\n\t\t\t\t\t\t\t1].height) {\n\t\t\t\t\t\tthis.activeIndex = -1\n\t\t\t\t\t\tbreak\n\t\t\t\t\t} else if (!nextItem) { \n\t\t\t\t\t\t// 当不存在下一个item时，意味着历遍到了最后一个\n\t\t\t\t\t\tthis.activeIndex = len - 1\n\t\t\t\t\t\tbreak\n\t\t\t\t\t} else if (scrollTop > item.top && scrollTop < nextItem.top) {\n\t\t\t\t\t\tthis.activeIndex = i\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</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-index-list {\n\n\t\t&__letter {\n\t\t\tposition: fixed;\n\t\t\tright: 0;\n\t\t\ttext-align: center;\n\t\t\tz-index: 3;\n\t\t\tpadding: 0 6px;\n\n\t\t\t&__item {\n\t\t\t\twidth: 16px;\n\t\t\t\theight: 16px;\n\t\t\t\tborder-radius: 100px;\n\t\t\t\tmargin: 1px 0;\n\t\t\t\t@include flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\n\t\t\t\t&--active {\n\t\t\t\t\tbackground-color: $u-primary;\n\t\t\t\t}\n\n\t\t\t\t&__index {\n\t\t\t\t\tfont-size: 12px;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t\tline-height: 12px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&__indicator {\n\t\t\twidth: 50px;\n\t\t\theight: 50px;\n\t\t\tborder-radius: 100px 100px 0 100px;\n\t\t\ttext-align: center;\n\t\t\tcolor: #ffffff;\n\t\t\tbackground-color: #c9c9c9;\n\t\t\ttransform: rotate(-45deg);\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\n\t\t\t&__text {\n\t\t\t\tfont-size: 28px;\n\t\t\t\tline-height: 28px;\n\t\t\t\tfont-weight: bold;\n\t\t\t\tcolor: #fff;\n\t\t\t\ttransform: rotate(45deg);\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-input/props.js",
    "content": "export default {\n\tprops: {\n\t\t// 输入的值\n\t\tvalue: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.input.value\n\t\t},\n\t\t// 输入框类型\n\t\t// number-数字输入键盘，app-vue下可以输入浮点数，app-nvue和小程序平台下只能输入整数\n\t\t// idcard-身份证输入键盘，微信、支付宝、百度、QQ小程序\n\t\t// digit-带小数点的数字键盘，App的nvue页面、微信、支付宝、百度、头条、QQ小程序\n\t\t// text-文本输入键盘\n\t\ttype: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.type\n\t\t},\n\t\t// 如果 textarea 是在一个 position:fixed 的区域，需要显示指定属性 fixed 为 true，\n\t\t// 兼容性：微信小程序、百度小程序、字节跳动小程序、QQ小程序\n\t\tfixed: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.fixed\n\t\t},\n\t\t// 是否禁用输入框\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.disabled\n\t\t},\n\t\t// 禁用状态时的背景色\n\t\tdisabledColor: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.disabledColor\n\t\t},\n\t\t// 是否显示清除控件\n\t\tclearable: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.clearable\n\t\t},\n\t\t// 是否密码类型\n\t\tpassword: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.password\n\t\t},\n\t\t// 最大输入长度，设置为 -1 的时候不限制最大长度\n\t\tmaxlength: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.input.maxlength\n\t\t},\n\t\t// \t输入框为空时的占位符\n\t\tplaceholder: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.placeholder\n\t\t},\n\t\t// 指定placeholder的样式类，注意页面或组件的style中写了scoped时，需要在类名前写/deep/\n\t\tplaceholderClass: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.placeholderClass\n\t\t},\n\t\t// 指定placeholder的样式\n\t\tplaceholderStyle: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: uni.$u.props.input.placeholderStyle\n\t\t},\n\t\t// 是否显示输入字数统计，只在 type =\"text\"或type =\"textarea\"时有效\n\t\tshowWordLimit: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.showWordLimit\n\t\t},\n\t\t// 设置右下角按钮的文字，有效值：send|search|next|go|done，兼容性详见uni-app文档\n\t\t// https://uniapp.dcloud.io/component/input\n\t\t// https://uniapp.dcloud.io/component/textarea\n\t\tconfirmType: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.confirmType\n\t\t},\n\t\t// 点击键盘右下角按钮时是否保持键盘不收起，H5无效\n\t\tconfirmHold: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.confirmHold\n\t\t},\n\t\t// focus时，点击页面的时候不收起键盘，微信小程序有效\n\t\tholdKeyboard: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.holdKeyboard\n\t\t},\n\t\t// 自动获取焦点\n\t\t// 在 H5 平台能否聚焦以及软键盘是否跟随弹出，取决于当前浏览器本身的实现。nvue 页面不支持，需使用组件的 focus()、blur() 方法控制焦点\n\t\tfocus: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.focus\n\t\t},\n\t\t// 键盘收起时，是否自动失去焦点，目前仅App3.0.0+有效\n\t\tautoBlur: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.autoBlur\n\t\t},\n\t\t// 是否去掉 iOS 下的默认内边距，仅微信小程序，且type=textarea时有效\n\t\tdisableDefaultPadding: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.disableDefaultPadding\n\t\t},\n\t\t// 指定focus时光标的位置\n\t\tcursor: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.input.cursor\n\t\t},\n\t\t// 输入框聚焦时底部与键盘的距离\n\t\tcursorSpacing: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.input.cursorSpacing\n\t\t},\n\t\t// 光标起始位置，自动聚集时有效，需与selection-end搭配使用\n\t\tselectionStart: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.input.selectionStart\n\t\t},\n\t\t// 光标结束位置，自动聚集时有效，需与selection-start搭配使用\n\t\tselectionEnd: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.input.selectionEnd\n\t\t},\n\t\t// 键盘弹起时，是否自动上推页面\n\t\tadjustPosition: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.adjustPosition\n\t\t},\n\t\t// 输入框内容对齐方式，可选值为：left|center|right\n\t\tinputAlign: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.inputAlign\n\t\t},\n\t\t// 输入框字体的大小\n\t\tfontSize: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.input.fontSize\n\t\t},\n\t\t// 输入框字体颜色\n\t\tcolor: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.color\n\t\t},\n\t\t// 输入框前置图标\n\t\tprefixIcon: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.prefixIcon\n\t\t},\n\t\t// 前置图标样式，对象或字符串\n\t\tprefixIconStyle: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: uni.$u.props.input.prefixIconStyle\n\t\t},\n\t\t// 输入框后置图标\n\t\tsuffixIcon: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.suffixIcon\n\t\t},\n\t\t// 后置图标样式，对象或字符串\n\t\tsuffixIconStyle: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: uni.$u.props.input.suffixIconStyle\n\t\t},\n\t\t// 边框类型，surround-四周边框，bottom-底部边框，none-无边框\n\t\tborder: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.border\n\t\t},\n\t\t// 是否只读，与disabled不同之处在于disabled会置灰组件，而readonly则不会\n\t\treadonly: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.input.readonly\n\t\t},\n\t\t// 输入框形状，circle-圆形，square-方形\n\t\tshape: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.shape\n\t\t},\n\t\t// 用于处理或者过滤输入框内容的方法\n\t\tformatter: {\n\t\t\ttype: [Function, null],\n\t\t\tdefault: uni.$u.props.input.formatter\n\t\t},\n\t\t// 是否忽略组件内对文本合成系统事件的处理\n\t\tignoreCompositionEvent: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-input/u-input.vue",
    "content": "<template>\n    <view class=\"u-input\" :class=\"inputClass\" :style=\"[wrapperStyle]\">\n        <view class=\"u-input__content\">\n            <view\n                class=\"u-input__content__prefix-icon\"\n                v-if=\"prefixIcon || $slots.prefix\"\n            >\n                <slot name=\"prefix\">\n                    <u-icon\n                        :name=\"prefixIcon\"\n                        size=\"18\"\n                        :customStyle=\"prefixIconStyle\"\n                    ></u-icon>\n                </slot>\n            </view>\n            <view class=\"u-input__content__field-wrapper\" @tap=\"clickHandler\">\n\t\t\t\t<!-- 根据uni-app的input组件文档，H5和APP中只要声明了password参数(无论true还是false)，type均失效，此时\n\t\t\t\t\t为了防止type=number时，又存在password属性，type无效，此时需要设置password为undefined\n\t\t\t\t -->\n            \t<input\n            \t    class=\"u-input__content__field-wrapper__field\"\n            \t    :style=\"[inputStyle]\"\n            \t    :type=\"type\"\n            \t    :focus=\"focus\"\n            \t    :cursor=\"cursor\"\n            \t    :value=\"innerValue\"\n            \t    :auto-blur=\"autoBlur\"\n            \t    :disabled=\"disabled || readonly\"\n            \t    :maxlength=\"maxlength\"\n            \t    :placeholder=\"placeholder\"\n            \t    :placeholder-style=\"placeholderStyle\"\n            \t    :placeholder-class=\"placeholderClass\"\n            \t    :confirm-type=\"confirmType\"\n            \t    :confirm-hold=\"confirmHold\"\n            \t    :hold-keyboard=\"holdKeyboard\"\n            \t    :cursor-spacing=\"cursorSpacing\"\n            \t    :adjust-position=\"adjustPosition\"\n            \t    :selection-end=\"selectionEnd\"\n            \t    :selection-start=\"selectionStart\"\n            \t    :password=\"password || type === 'password' || undefined\"\n                    :ignoreCompositionEvent=\"ignoreCompositionEvent\"\n            \t    @input=\"onInput\"\n            \t    @blur=\"onBlur\"\n            \t    @focus=\"onFocus\"\n            \t    @confirm=\"onConfirm\"\n            \t    @keyboardheightchange=\"onkeyboardheightchange\"\n            \t/>\n            </view>\n            <view\n                class=\"u-input__content__clear\"\n                v-if=\"isShowClear\"\n                @tap=\"onClear\"\n            >\n                <u-icon\n                    name=\"close\"\n                    size=\"11\"\n                    color=\"#ffffff\"\n                    customStyle=\"line-height: 12px\"\n                ></u-icon>\n            </view>\n            <view\n                class=\"u-input__content__subfix-icon\"\n                v-if=\"suffixIcon || $slots.suffix\"\n            >\n                <slot name=\"suffix\">\n                    <u-icon\n                        :name=\"suffixIcon\"\n                        size=\"18\"\n                        :customStyle=\"suffixIconStyle\"\n                    ></u-icon>\n                </slot>\n            </view>\n        </view>\n    </view>\n</template>\n\n<script>\nimport props from \"./props.js\";\n/**\n * Input 输入框\n * @description  此组件为一个输入框，默认没有边框和样式，是专门为配合表单组件u-form而设计的，利用它可以快速实现表单验证，输入内容，下拉选择等功能。\n * @tutorial https://uviewui.com/components/input.html\n * @property {String | Number}\tvalue\t\t\t\t\t输入的值\n * @property {String}\t\t\ttype\t\t\t\t\t输入框类型，见上方说明 （ 默认 'text' ）\n * @property {Boolean}\t\t\tfixed\t\t\t\t\t如果 textarea 是在一个 position:fixed 的区域，需要显示指定属性 fixed 为 true，兼容性：微信小程序、百度小程序、字节跳动小程序、QQ小程序 （ 默认 false ）\n * @property {Boolean}\t\t\tdisabled\t\t\t\t是否禁用输入框 （ 默认 false ）\n * @property {String}\t\t\tdisabledColor\t\t\t禁用状态时的背景色（ 默认 '#f5f7fa' ）\n * @property {Boolean}\t\t\tclearable\t\t\t\t是否显示清除控件 （ 默认 false ）\n * @property {Boolean}\t\t\tpassword\t\t\t\t是否密码类型 （ 默认 false ）\n * @property {String | Number}\tmaxlength\t\t\t\t最大输入长度，设置为 -1 的时候不限制最大长度 （ 默认 -1 ）\n * @property {String}\t\t\tplaceholder\t\t\t\t输入框为空时的占位符\n * @property {String}\t\t\tplaceholderClass\t\t指定placeholder的样式类，注意页面或组件的style中写了scoped时，需要在类名前写/deep/ （ 默认 'input-placeholder' ）\n * @property {String | Object}\tplaceholderStyle\t\t指定placeholder的样式，字符串/对象形式，如\"color: red;\"\n * @property {Boolean}\t\t\tshowWordLimit\t\t\t是否显示输入字数统计，只在 type =\"text\"或type =\"textarea\"时有效 （ 默认 false ）\n * @property {String}\t\t\tconfirmType\t\t\t\t设置右下角按钮的文字，兼容性详见uni-app文档 （ 默认 'done' ）\n * @property {Boolean}\t\t\tconfirmHold\t\t\t\t点击键盘右下角按钮时是否保持键盘不收起，H5无效 （ 默认 false ）\n * @property {Boolean}\t\t\tholdKeyboard\t\t\tfocus时，点击页面的时候不收起键盘，微信小程序有效 （ 默认 false ）\n * @property {Boolean}\t\t\tfocus\t\t\t\t\t自动获取焦点，在 H5 平台能否聚焦以及软键盘是否跟随弹出，取决于当前浏览器本身的实现。nvue 页面不支持，需使用组件的 focus()、blur() 方法控制焦点 （ 默认 false ）\n * @property {Boolean}\t\t\tautoBlur\t\t\t\t键盘收起时，是否自动失去焦点，目前仅App3.0.0+有效 （ 默认 false ）\n * @property {Boolean}\t\t\tdisableDefaultPadding\t是否去掉 iOS 下的默认内边距，仅微信小程序，且type=textarea时有效 （ 默认 false ）\n * @property {String ｜ Number}\tcursor\t\t\t\t\t指定focus时光标的位置（ 默认 -1 ）\n * @property {String ｜ Number}\tcursorSpacing\t\t\t输入框聚焦时底部与键盘的距离 （ 默认 30 ）\n * @property {String ｜ Number}\tselectionStart\t\t\t光标起始位置，自动聚集时有效，需与selection-end搭配使用 （ 默认 -1 ）\n * @property {String ｜ Number}\tselectionEnd\t\t\t光标结束位置，自动聚集时有效，需与selection-start搭配使用 （ 默认 -1 ）\n * @property {Boolean}\t\t\tadjustPosition\t\t\t键盘弹起时，是否自动上推页面 （ 默认 true ）\n * @property {String}\t\t\tinputAlign\t\t\t\t输入框内容对齐方式（ 默认 'left' ）\n * @property {String | Number}\tfontSize\t\t\t\t输入框字体的大小 （ 默认 '15px' ）\n * @property {String}\t\t\tcolor\t\t\t\t\t输入框字体颜色\t（ 默认 '#303133' ）\n * @property {Function}\t\t\tformatter\t\t\t    内容式化函数\n * @property {String}\t\t\tprefixIcon\t\t\t\t输入框前置图标\n * @property {String | Object}\tprefixIconStyle\t\t\t前置图标样式，对象或字符串\n * @property {String}\t\t\tsuffixIcon\t\t\t\t输入框后置图标\n * @property {String | Object}\tsuffixIconStyle\t\t\t后置图标样式，对象或字符串\n * @property {String}\t\t\tborder\t\t\t\t\t边框类型，surround-四周边框，bottom-底部边框，none-无边框 （ 默认 'surround' ）\n * @property {Boolean}\t\t\treadonly\t\t\t\t是否只读，与disabled不同之处在于disabled会置灰组件，而readonly则不会 （ 默认 false ）\n * @property {String}\t\t\tshape\t\t\t\t\t输入框形状，circle-圆形，square-方形 （ 默认 'square' ）\n * @property {Object}\t\t\tcustomStyle\t\t\t\t定义需要用到的外部样式\n * @property {Boolean}\t\t\tignoreCompositionEvent\t是否忽略组件内对文本合成系统事件的处理。\n * @example <u-input v-model=\"value\" :password=\"true\" suffix-icon=\"lock-fill\" />\n */\nexport default {\n    name: \"u-input\",\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n    data() {\n        return {\n            // 输入框的值\n            innerValue: \"\",\n            // 是否处于获得焦点状态\n            focused: false,\n            // value是否第一次变化，在watch中，由于加入immediate属性，会在第一次触发，此时不应该认为value发生了变化\n            firstChange: true,\n            // value绑定值的变化是由内部还是外部引起的\n            changeFromInner: false,\n\t\t\t// 过滤处理方法\n\t\t\tinnerFormatter: value => value\n        };\n    },\n    watch: {\n        value: {\n            immediate: true,\n            handler(newVal, oldVal) {\n                this.innerValue = newVal;\n                /* #ifdef H5 */\n                // 在H5中，外部value变化后，修改input中的值，不会触发@input事件，此时手动调用值变化方法\n                if (\n                    this.firstChange === false &&\n                    this.changeFromInner === false\n                ) {\n                    this.valueChange();\n                }\n                /* #endif */\n                this.firstChange = false;\n                // 重置changeFromInner的值为false，标识下一次引起默认为外部引起的\n                this.changeFromInner = false;\n            },\n        },\n    },\n    computed: {\n        // 是否显示清除控件\n        isShowClear() {\n            const { clearable, readonly, focused, innerValue } = this;\n            return !!clearable && !readonly && !!focused && innerValue !== \"\";\n        },\n        // 组件的类名\n        inputClass() {\n            let classes = [],\n                { border, disabled, shape } = this;\n            border === \"surround\" &&\n                (classes = classes.concat([\"u-border\", \"u-input--radius\"]));\n            classes.push(`u-input--${shape}`);\n            border === \"bottom\" &&\n                (classes = classes.concat([\n                    \"u-border-bottom\",\n                    \"u-input--no-radius\",\n                ]));\n            return classes.join(\" \");\n        },\n        // 组件的样式\n        wrapperStyle() {\n            const style = {};\n            // 禁用状态下，被背景色加上对应的样式\n            if (this.disabled) {\n                style.backgroundColor = this.disabledColor;\n            }\n            // 无边框时，去除内边距\n            if (this.border === \"none\") {\n                style.padding = \"0\";\n            } else {\n                // 由于uni-app的iOS开发者能力有限，导致需要分开写才有效\n                style.paddingTop = \"6px\";\n                style.paddingBottom = \"6px\";\n                style.paddingLeft = \"9px\";\n                style.paddingRight = \"9px\";\n            }\n            return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));\n        },\n        // 输入框的样式\n        inputStyle() {\n            const style = {\n                color: this.color,\n                fontSize: uni.$u.addUnit(this.fontSize),\n\t\t\t\ttextAlign: this.inputAlign\n            };\n            return style;\n        },\n    },\n    methods: {\n\t\t// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用\n\t\tsetFormatter(e) {\n\t\t\tthis.innerFormatter = e\n\t\t},\n        // 当键盘输入时，触发input事件\n        onInput(e) {\n            let { value = \"\" } = e.detail || {};\n            // 格式化过滤方法\n            const formatter = this.formatter || this.innerFormatter\n            const formatValue = formatter(value)\n            // 为了避免props的单向数据流特性，需要先将innerValue值设置为当前值，再在$nextTick中重新赋予设置后的值才有效\n            this.innerValue = value\n            this.$nextTick(() => {\n            \tthis.innerValue = formatValue;\n            \tthis.valueChange();\n            })\n        },\n        // 输入框失去焦点时触发\n        onBlur(event) {\n            this.$emit(\"blur\", event.detail.value);\n            // H5端的blur会先于点击清除控件的点击click事件触发，导致focused\n            // 瞬间为false，从而隐藏了清除控件而无法被点击到\n            uni.$u.sleep(50).then(() => {\n                this.focused = false;\n            });\n            // 尝试调用u-form的验证方法\n            uni.$u.formValidate(this, \"blur\");\n        },\n        // 输入框聚焦时触发\n        onFocus(event) {\n            this.focused = true;\n            this.$emit(\"focus\");\n        },\n        // 点击完成按钮时触发\n        onConfirm(event) {\n            this.$emit(\"confirm\", this.innerValue);\n        },\n        // 键盘高度发生变化的时候触发此事件\n        // 兼容性：微信小程序2.7.0+、App 3.1.0+\n\t\tonkeyboardheightchange() {\n            this.$emit(\"keyboardheightchange\");\n        },\n        // 内容发生变化，进行处理\n        valueChange() {\n            const value = this.innerValue;\n            this.$nextTick(() => {\n                this.$emit(\"input\", value);\n                // 标识value值的变化是由内部引起的\n                this.changeFromInner = true;\n                this.$emit(\"change\", value);\n                // 尝试调用u-form的验证方法\n                uni.$u.formValidate(this, \"change\");\n            });\n        },\n        // 点击清除控件\n        onClear() {\n            this.innerValue = \"\";\n            this.$nextTick(() => {\n                this.valueChange();\n                this.$emit(\"clear\");\n            });\n        },\n        /**\n         * 在安卓nvue上，事件无法冒泡\n         * 在某些时间，我们希望监听u-from-item的点击事件，此时会导致点击u-form-item内的u-input后\n         * 无法触发u-form-item的点击事件，这里通过手动调用u-form-item的方法进行触发\n         */\n        clickHandler() {\n            // #ifdef APP-NVUE\n            if (uni.$u.os() === \"android\") {\n                const formItem = uni.$u.$parent.call(this, \"u-form-item\");\n                if (formItem) {\n                    formItem.clickHandler();\n                }\n            }\n            // #endif\n        },\n    },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n\n.u-input {\n    @include flex(row);\n    align-items: center;\n    justify-content: space-between;\n    flex: 1;\n\n    &--radius,\n    &--square {\n        border-radius: 4px;\n    }\n\n    &--no-radius {\n        border-radius: 0;\n    }\n\n    &--circle {\n        border-radius: 100px;\n    }\n\n    &__content {\n        flex: 1;\n        @include flex(row);\n        align-items: center;\n        justify-content: space-between;\n\n        &__field-wrapper {\n            position: relative;\n            @include flex(row);\n            margin: 0;\n            flex: 1;\n\t\t\t\n\t\t\t&__field {\n\t\t\t\tline-height: 26px;\n\t\t\t\ttext-align: left;\n\t\t\t\tcolor: $u-main-color;\n\t\t\t\theight: 24px;\n\t\t\t\tfont-size: 15px;\n\t\t\t\tflex: 1;\n\t\t\t}\n        }\n\n        &__clear {\n            width: 20px;\n            height: 20px;\n            border-radius: 100px;\n            background-color: #c6c7cb;\n            @include flex(row);\n            align-items: center;\n            justify-content: center;\n            transform: scale(0.82);\n            margin-left: 4px;\n        }\n\n        &__subfix-icon {\n            margin-left: 4px;\n        }\n\n        &__prefix-icon {\n            margin-right: 4px;\n        }\n    }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-keyboard/props.js",
    "content": "export default {\n    props: {\n        // 键盘的类型，number-数字键盘，card-身份证键盘，car-车牌号键盘\n        mode: {\n            type: String,\n            default: uni.$u.props.keyboard.mode\n        },\n        // 是否显示键盘的\".\"符号\n        dotDisabled: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.dotDisabled\n        },\n        // 是否显示顶部工具条\n        tooltip: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.tooltip\n        },\n        // 是否显示工具条中间的提示\n        showTips: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.showTips\n        },\n        // 工具条中间的提示文字\n        tips: {\n            type: String,\n            default: uni.$u.props.keyboard.tips\n        },\n        // 是否显示工具条左边的\"取消\"按钮\n        showCancel: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.showCancel\n        },\n        // 是否显示工具条右边的\"完成\"按钮\n        showConfirm: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.showConfirm\n        },\n        // 是否打乱键盘按键的顺序\n        random: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.random\n        },\n        // 是否开启底部安全区适配，开启的话，会在iPhoneX机型底部添加一定的内边距\n        safeAreaInsetBottom: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.safeAreaInsetBottom\n        },\n        // 是否允许通过点击遮罩关闭键盘\n        closeOnClickOverlay: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.closeOnClickOverlay\n        },\n        // 控制键盘的弹出与收起\n        show: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.show\n        },\n        // 是否显示遮罩，某些时候数字键盘时，用户希望看到自己的数值，所以可能不想要遮罩\n        overlay: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.overlay\n        },\n        // z-index值\n        zIndex: {\n            type: [String, Number],\n            default: uni.$u.props.keyboard.zIndex\n        },\n        // 取消按钮的文字\n        cancelText: {\n            type: String,\n            default: uni.$u.props.keyboard.cancelText\n        },\n        // 确认按钮的文字\n        confirmText: {\n            type: String,\n            default: uni.$u.props.keyboard.confirmText\n        },\n        // 输入一个中文后，是否自动切换到英文\n        autoChange: {\n            type: Boolean,\n            default: uni.$u.props.keyboard.autoChange\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-keyboard/u-keyboard.vue",
    "content": "<template>\n\t<u-popup\n\t    :overlay=\"overlay\"\n\t    :closeOnClickOverlay=\"closeOnClickOverlay\"\n\t    mode=\"bottom\"\n\t    :popup=\"false\"\n\t    :show=\"show\"\n\t    :safeAreaInsetBottom=\"safeAreaInsetBottom\"\n\t    @close=\"popupClose\"\n\t    :zIndex=\"zIndex\"\n\t    :customStyle=\"{\n\t\t\tbackgroundColor: 'rgb(214, 218, 220)'\n\t\t}\"\n\t>\n\t\t<view class=\"u-keyboard\">\n\t\t\t<slot />\n\t\t\t<view\n\t\t\t    class=\"u-keyboard__tooltip\"\n\t\t\t    v-if=\"tooltip\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t    hover-class=\"u-hover-class\"\n\t\t\t\t    :hover-stay-time=\"100\"\n\t\t\t\t>\n\t\t\t\t\t<text\n\t\t\t\t\t    class=\"u-keyboard__tooltip__item u-keyboard__tooltip__cancel\"\n\t\t\t\t\t    v-if=\"showCancel\"\n\t\t\t\t\t    @tap=\"onCancel\"\n\t\t\t\t\t>{{showCancel && cancelText}}</text>\n\t\t\t\t</view>\n\t\t\t\t<view>\n\t\t\t\t\t<text\n\t\t\t\t\t    v-if=\"showTips\"\n\t\t\t\t\t    class=\"u-keyboard__tooltip__item u-keyboard__tooltip__tips\"\n\t\t\t\t\t>{{tips ? tips : mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}}</text>\n\t\t\t\t</view>\n\t\t\t\t<view\n\t\t\t\t    hover-class=\"u-hover-class\"\n\t\t\t\t    :hover-stay-time=\"100\"\n\t\t\t\t>\n\t\t\t\t\t<text\n\t\t\t\t\t    v-if=\"showConfirm\"\n\t\t\t\t\t    @tap=\"onConfirm\"\n\t\t\t\t\t    class=\"u-keyboard__tooltip__item u-keyboard__tooltip__submit\"\n\t\t\t\t\t    hover-class=\"u-hover-class\"\n\t\t\t\t\t>{{showConfirm && confirmText}}</text>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<template v-if=\"mode == 'number' || mode == 'card'\">\n\t\t\t\t<u-number-keyboard\n\t\t\t\t    :random=\"random\"\n\t\t\t\t    @backspace=\"backspace\"\n\t\t\t\t    @change=\"change\"\n\t\t\t\t    :mode=\"mode\"\n\t\t\t\t    :dotDisabled=\"dotDisabled\"\n\t\t\t\t></u-number-keyboard>\n\t\t\t</template>\n\t\t\t<template v-else>\n\t\t\t\t<u-car-keyboard\n\t\t\t\t    :random=\"random\"\n\t\t\t\t\t:autoChange=\"autoChange\"\n\t\t\t\t    @backspace=\"backspace\"\n\t\t\t\t    @change=\"change\"\n\t\t\t\t></u-car-keyboard>\n\t\t\t</template>\n\t\t</view>\n\t</u-popup>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * keyboard 键盘\n\t * @description 此为uViw自定义的键盘面板，内含了数字键盘，车牌号键，身份证号键盘3中模式，都有可以打乱按键顺序的选项。\n\t * @tutorial https://www.uviewui.com/components/keyboard.html\n\t * @property {String}\t\t\tmode\t\t\t\t键盘类型，见官网基本使用的说明 （默认 'number' ）\n\t * @property {Boolean}\t\t\tdotDisabled\t\t\t是否显示\".\"按键，只在mode=number时有效 （默认 false ）\n\t * @property {Boolean}\t\t\ttooltip\t\t\t\t是否显示键盘顶部工具条 （默认 true ）\n\t * @property {Boolean}\t\t\tshowTips\t\t\t是否显示工具条中间的提示 （默认 true ）\n\t * @property {String}\t\t\ttips\t\t\t\t工具条中间的提示文字，见上方基本使用的说明，如不需要，请传\"\"空字符\n\t * @property {Boolean}\t\t\tshowCancel\t\t\t是否显示工具条左边的\"取消\"按钮 （默认 true ）\n\t * @property {Boolean}\t\t\tshowConfirm\t\t\t是否显示工具条右边的\"完成\"按钮（ 默认 true ）\n\t * @property {Boolean}\t\t\trandom\t\t\t\t是否打乱键盘按键的顺序 （默认 false ）\n\t * @property {Boolean}\t\t\tsafeAreaInsetBottom\t是否开启底部安全区适配 （默认 true ）\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t是否允许点击遮罩收起键盘 （默认 true ）\n\t * @property {Boolean}\t\t\tshow\t\t\t\t控制键盘的弹出与收起（默认 false ）\n\t * @property {Boolean}\t\t\toverlay\t\t\t\t是否显示遮罩 （默认 true ）\n\t * @property {String | Number}\tzIndex\t\t\t\t弹出键盘的z-index值 （默认 1075 ）\n\t * @property {String}\t\t\tcancelText\t\t\t取消按钮的文字 （默认 '取消' ）\n\t * @property {String}\t\t\tconfirmText\t\t\t确认按钮的文字 （默认 '确认' ）\n\t * @property {Object}\t\t\tcustomStyle\t\t\t自定义样式，对象形式\n\t * @event {Function} change 按键被点击(不包含退格键被点击)\n\t * @event {Function} cancel 键盘顶部工具条左边的\"取消\"按钮被点击\n\t * @event {Function} confirm 键盘顶部工具条右边的\"完成\"按钮被点击\n\t * @event {Function} backspace 键盘退格键被点击\n\t * @example <u-keyboard mode=\"number\" v-model=\"show\"></u-keyboard>\n\t */\n\texport default {\n\t\tname: \"u-keyboard\",\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tmethods: {\n\t\t\tchange(e) {\n\t\t\t\tthis.$emit('change', e);\n\t\t\t},\n\t\t\t// 键盘关闭\n\t\t\tpopupClose() {\n\t\t\t\tthis.$emit('close');\n\t\t\t},\n\t\t\t// 输入完成\n\t\t\tonConfirm() {\n\t\t\t\tthis.$emit('confirm');\n\t\t\t},\n\t\t\t// 取消输入\n\t\t\tonCancel() {\n\t\t\t\tthis.$emit('cancel');\n\t\t\t},\n\t\t\t// 退格键\n\t\t\tbackspace() {\n\t\t\t\tthis.$emit('backspace');\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-keyboard {\n\n\t\t&__tooltip {\n\t\t\t@include flex;\n\t\t\tjustify-content: space-between;\n\t\t\tbackground-color: #FFFFFF;\n\t\t\tpadding: 14px 12px;\n\n\t\t\t&__item {\n\t\t\t\tcolor: #333333;\n\t\t\t\tflex: 1;\n\t\t\t\ttext-align: center;\n\t\t\t\tfont-size: 15px;\n\t\t\t}\n\n\t\t\t&__submit {\n\t\t\t\ttext-align: right;\n\t\t\t\tcolor: $u-primary;\n\t\t\t}\n\n\t\t\t&__cancel {\n\t\t\t\ttext-align: left;\n\t\t\t\tcolor: #888888;\n\t\t\t}\n\n\t\t\t&__tips {\n\t\t\t\tcolor: $u-tips-color;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-line/props.js",
    "content": "export default {\n    props: {\n        color: {\n            type: String,\n            default: uni.$u.props.line.color\n        },\n        // 长度，竖向时表现为高度，横向时表现为长度，可以为百分比，带px单位的值等\n        length: {\n            type: [String, Number],\n            default: uni.$u.props.line.length\n        },\n        // 线条方向，col-竖向，row-横向\n        direction: {\n            type: String,\n            default: uni.$u.props.line.direction\n        },\n        // 是否显示细边框\n        hairline: {\n            type: Boolean,\n            default: uni.$u.props.line.hairline\n        },\n        // 线条与上下左右元素的间距，字符串形式，如\"30px\"、\"20px 30px\"\n        margin: {\n            type: [String, Number],\n            default: uni.$u.props.line.margin\n        },\n        // 是否虚线，true-虚线，false-实线\n        dashed: {\n            type: Boolean,\n            default: uni.$u.props.line.dashed\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-line/u-line.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-line\"\n\t    :style=\"[lineStyle]\"\n\t>\n\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * line 线条\n\t * @description 此组件一般用于显示一根线条，用于分隔内容块，有横向和竖向两种模式，且能设置0.5px线条，使用也很简单\n\t * @tutorial https://www.uviewui.com/components/line.html\n\t * @property {String}\t\t\tcolor\t\t线条的颜色 ( 默认 '#d6d7d9' )\n\t * @property {String | Number}\tlength\t\t长度，竖向时表现为高度，横向时表现为长度，可以为百分比，带px单位的值等 ( 默认 '100%' )\n\t * @property {String}\t\t\tdirection\t线条的方向，row-横向，col-竖向 (默认 'row' )\n\t * @property {Boolean}\t\t\thairline\t是否显示细线条 (默认 true )\n\t * @property {String | Number}\tmargin\t\t线条与上下左右元素的间距，字符串形式，如\"30px\"  (默认 0 )\n\t * @property {Boolean}\t\t\tdashed\t\t是否虚线，true-虚线，false-实线 (默认 false )\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * @example <u-line color=\"red\"></u-line>\n\t */\n\texport default {\n\t\tname: 'u-line',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\tlineStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.margin = this.margin\n\t\t\t\t// 如果是水平线条，边框高度为1px，再通过transform缩小一半，就是0.5px了\n\t\t\t\tif (this.direction === 'row') {\n\t\t\t\t\t// 此处采用兼容分开写，兼容nvue的写法\n\t\t\t\t\tstyle.borderBottomWidth = '1px'\n\t\t\t\t\tstyle.borderBottomStyle = this.dashed ? 'dashed' : 'solid'\n\t\t\t\t\tstyle.width = uni.$u.addUnit(this.length)\n\t\t\t\t\tif (this.hairline) style.transform = 'scaleY(0.5)'\n\t\t\t\t} else {\n\t\t\t\t\t// 如果是竖向线条，边框宽度为1px，再通过transform缩小一半，就是0.5px了\n\t\t\t\t\tstyle.borderLeftWidth = '1px'\n\t\t\t\t\tstyle.borderLeftStyle = this.dashed ? 'dashed' : 'solid'\n\t\t\t\t\tstyle.height = uni.$u.addUnit(this.length)\n\t\t\t\t\tif (this.hairline) style.transform = 'scaleX(0.5)'\n\t\t\t\t}\n\n\t\t\t\tstyle.borderColor = this.color\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-line {\n\t\t/* #ifndef APP-NVUE */\n\t\tvertical-align: middle;\n\t\t/* #endif */\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-line-progress/props.js",
    "content": "export default {\n    props: {\n        // 激活部分的颜色\n        activeColor: {\n            type: String,\n            default: uni.$u.props.lineProgress.activeColor\n        },\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.lineProgress.color\n        },\n        // 进度百分比，数值\n        percentage: {\n            type: [String, Number],\n            default: uni.$u.props.lineProgress.inactiveColor\n        },\n        // 是否在进度条内部显示百分比的值\n        showText: {\n            type: Boolean,\n            default: uni.$u.props.lineProgress.showText\n        },\n        // 进度条的高度，单位px\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.lineProgress.height\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-line-progress/u-line-progress.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-line-progress\"\n\t    :style=\"[$u.addStyle(customStyle)]\"\n\t>\n\t\t<view\n\t\t    class=\"u-line-progress__background\"\n\t\t    ref=\"u-line-progress__background\"\n\t\t    :style=\"[{\n\t\t\t\tbackgroundColor: inactiveColor,\n\t\t\t\theight: $u.addUnit(height),\n\t\t\t}]\"\n\t\t>\n\t\t</view>\n\t\t<view\n\t\t    class=\"u-line-progress__line\"\n\t\t    :style=\"[progressStyle]\"\n\t\t> \n\t\t\t<slot>\n\t\t\t\t<text v-if=\"showText && percentage >= 10\" class=\"u-line-progress__text\">{{innserPercentage + '%'}}</text>\n\t\t\t</slot> \n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * lineProgress 线型进度条\n\t * @description 展示操作或任务的当前进度，比如上传文件，是一个线形的进度条。\n\t * @tutorial https://www.uviewui.com/components/lineProgress.html\n\t * @property {String}\t\t\tactiveColor\t\t激活部分的颜色 ( 默认 '#19be6b' )\n\t * @property {String}\t\t\tinactiveColor\t背景色 ( 默认 '#ececec' )\n\t * @property {String | Number}\tpercentage\t\t进度百分比，数值 ( 默认 0 )\n\t * @property {Boolean}\t\t\tshowText\t\t是否在进度条内部显示百分比的值 ( 默认 true )\n\t * @property {String | Number}\theight\t\t\t进度条的高度，单位px ( 默认 12 )\n\t * \n\t * @example <u-line-progress :percent=\"70\" :show-percent=\"true\"></u-line-progress>\n\t */\n\texport default {\n\t\tname: \"u-line-progress\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tlineWidth: 0,\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tpercentage(n) {\n\t\t\t\tthis.resizeProgressWidth()\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tprogressStyle() { \n\t\t\t\tlet style = {}\n\t\t\t\tstyle.width = this.lineWidth\n\t\t\t\tstyle.backgroundColor = this.activeColor\n\t\t\t\tstyle.height = uni.$u.addUnit(this.height)\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tinnserPercentage() {\n\t\t\t\t// 控制范围在0-100之间\n\t\t\t\treturn uni.$u.range(0, 100, this.percentage)\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tuni.$u.sleep(20).then(() => {\n\t\t\t\t\tthis.resizeProgressWidth()\n\t\t\t\t})\n\t\t\t},\n\t\t\tgetProgressWidth() {\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\treturn this.$uGetRect('.u-line-progress__background')\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// 返回一个promise\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tdom.getComponentRect(this.$refs['u-line-progress__background'], (res) => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\tresizeProgressWidth() {\n\t\t\t\tthis.getProgressWidth().then(size => {\n\t\t\t\t\tconst {\n\t\t\t\t\t\twidth\n\t\t\t\t\t} = size\n\t\t\t\t\t// 通过设置的percentage值，计算其所占总长度的百分比\n\t\t\t\t\tthis.lineWidth = width * this.innserPercentage / 100 + 'px'\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-line-progress {\n\t\talign-items: stretch;\n\t\tposition: relative;\n\t\t@include flex(row);\n\t\tflex: 1;\n\t\toverflow: hidden;\n\t\tborder-radius: 100px;\n\n\t\t&__background {\n\t\t\tbackground-color: #ececec;\n\t\t\tborder-radius: 100px;\n\t\t\tflex: 1;\n\t\t}\n\n\t\t&__line {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tleft: 0;\n\t\t\tbottom: 0;\n\t\t\talign-items: center;\n\t\t\t@include flex(row);\n\t\t\tcolor: #ffffff;\n\t\t\tborder-radius: 100px;\n\t\t\ttransition: width 0.5s ease;\n\t\t\tjustify-content: flex-end;\n\t\t}\n\n\t\t&__text {\n\t\t\tfont-size: 10px;\n\t\t\talign-items: center;\n\t\t\ttext-align: right;\n\t\t\tcolor: #FFFFFF;\n\t\t\tmargin-right: 5px;\n\t\t\ttransform: scale(0.9);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-link/props.js",
    "content": "export default {\n    props: {\n        // 文字颜色\n        color: {\n            type: String,\n            default: uni.$u.props.link.color\n        },\n        // 字体大小，单位px\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.link.fontSize\n        },\n        // 是否显示下划线\n        underLine: {\n            type: Boolean,\n            default: uni.$u.props.link.underLine\n        },\n        // 要跳转的链接\n        href: {\n            type: String,\n            default: uni.$u.props.link.href\n        },\n        // 小程序中复制到粘贴板的提示语\n        mpTips: {\n            type: String,\n            default: uni.$u.props.link.mpTips\n        },\n        // 下划线颜色\n        lineColor: {\n            type: String,\n            default: uni.$u.props.link.lineColor\n        },\n        // 超链接的问题，不使用slot形式传入，是因为nvue下无法修改颜色\n        text: {\n            type: String,\n            default: uni.$u.props.link.text\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-link/u-link.vue",
    "content": "<template>\n\t<text\n\t    class=\"u-link\"\n\t    @tap.stop=\"openLink\"\n\t    :style=\"[linkStyle, $u.addStyle(customStyle)]\"\n\t>{{text}}</text>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * link 超链接\n\t * @description 该组件为超链接组件，在不同平台有不同表现形式：在APP平台会通过plus环境打开内置浏览器，在小程序中把链接复制到粘贴板，同时提示信息，在H5中通过window.open打开链接。\n\t * @tutorial https://www.uviewui.com/components/link.html\n\t * @property {String}\t\t\tcolor\t\t文字颜色 （默认 color['u-primary'] ）\n\t * @property {String ｜ Number}\tfontSize\t字体大小，单位px （默认 15 ）\n\t * @property {Boolean}\t\t\tunderLine\t是否显示下划线 （默认 false ）\n\t * @property {String}\t\t\thref\t\t跳转的链接，要带上http(s)\n\t * @property {String}\t\t\tmpTips\t\t各个小程序平台把链接复制到粘贴板后的提示语（默认“链接已复制，请在浏览器打开”）\n\t * @property {String}\t\t\tlineColor\t下划线颜色，默认同color参数颜色 \n\t * @property {String}\t\t\ttext\t\t超链接的问题，不使用slot形式传入，是因为nvue下无法修改颜色 \n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * \n\t * @example <u-link href=\"http://www.uviewui.com\">蜀道难，难于上青天</u-link>\n\t */\n\texport default {\n\t\tname: \"u-link\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\tlinkStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tcolor: this.color,\n\t\t\t\t\tfontSize: uni.$u.addUnit(this.fontSize),\n\t\t\t\t\t// line-height设置为比字体大小多2px\n\t\t\t\t\tlineHeight: uni.$u.addUnit(uni.$u.getPx(this.fontSize) + 2),\n\t\t\t\t\ttextDecoration: this.underLine ? 'underline' : 'none'\n\t\t\t\t}\n\t\t\t\t// if (this.underLine) {\n\t\t\t\t// \tstyle.borderBottomColor = this.lineColor || this.color\n\t\t\t\t// \tstyle.borderBottomWidth = '1px'\n\t\t\t\t// }\n\t\t\t\treturn style\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\topenLink() {\n\t\t\t\t// #ifdef APP-PLUS\n\t\t\t\tplus.runtime.openURL(this.href)\n\t\t\t\t// #endif\n\t\t\t\t// #ifdef H5\n\t\t\t\twindow.open(this.href)\n\t\t\t\t// #endif\n\t\t\t\t// #ifdef MP\n\t\t\t\tuni.setClipboardData({\n\t\t\t\t\tdata: this.href,\n\t\t\t\t\tsuccess: () => {\n\t\t\t\t\t\tuni.hideToast();\n\t\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\t\tuni.$u.toast(this.mpTips);\n\t\t\t\t\t\t})\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\t// #endif\n\t\t\t\tthis.$emit('click')\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-link-line-height:1 !default;\n\n\t.u-link {\n\t\t/* #ifndef APP-NVUE */\n\t\tline-height: $u-link-line-height;\n\t\t/* #endif */\n\t\t@include flex;\n\t\tflex-wrap: wrap;\n\t\tflex: 1;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-list/props.js",
    "content": "export default {\n    props: {\n        // 控制是否出现滚动条，仅nvue有效\n        showScrollbar: {\n            type: Boolean,\n            default: uni.$u.props.list.showScrollbar\n        },\n        // 距底部多少时触发scrolltolower事件\n        lowerThreshold: {\n            type: [String, Number],\n            default: uni.$u.props.list.lowerThreshold\n        },\n        // 距顶部多少时触发scrolltoupper事件，非nvue有效\n        upperThreshold: {\n            type: [String, Number],\n            default: uni.$u.props.list.upperThreshold\n        },\n        // 设置竖向滚动条位置\n        scrollTop: {\n            type: [String, Number],\n            default: uni.$u.props.list.scrollTop\n        },\n        // 控制 onscroll 事件触发的频率，仅nvue有效\n        offsetAccuracy: {\n            type: [String, Number],\n            default: uni.$u.props.list.offsetAccuracy\n        },\n        // 启用 flexbox 布局。开启后，当前节点声明了display: flex就会成为flex container，并作用于其孩子节点，仅微信小程序有效\n        enableFlex: {\n            type: Boolean,\n            default: uni.$u.props.list.enableFlex\n        },\n        // 是否按分页模式显示List，默认值false\n        pagingEnabled: {\n            type: Boolean,\n            default: uni.$u.props.list.pagingEnabled\n        },\n        // 是否允许List滚动\n        scrollable: {\n            type: Boolean,\n            default: uni.$u.props.list.scrollable\n        },\n        // 值应为某子元素id（id不能以数字开头）\n        scrollIntoView: {\n            type: String,\n            default: uni.$u.props.list.scrollIntoView\n        },\n        // 在设置滚动条位置时使用动画过渡\n        scrollWithAnimation: {\n            type: Boolean,\n            default: uni.$u.props.list.scrollWithAnimation\n        },\n        // iOS点击顶部状态栏、安卓双击标题栏时，滚动条返回顶部，只对微信小程序有效\n        enableBackToTop: {\n            type: Boolean,\n            default: uni.$u.props.list.enableBackToTop\n        },\n        // 列表的高度\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.list.height\n        },\n        // 列表宽度\n        width: {\n            type: [String, Number],\n            default: uni.$u.props.list.width\n        },\n        // 列表前后预渲染的屏数，1代表一个屏幕的高度，1.5代表1个半屏幕高度\n        preLoadScreen: {\n            type: [String, Number],\n            default: uni.$u.props.list.preLoadScreen\n        }\n        // vue下，是否开启虚拟列表\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-list/u-list.vue",
    "content": "<template>\n\t<!-- #ifdef APP-NVUE -->\n\t<list\n\t\tclass=\"u-list\"\n\t\t:enableBackToTop=\"enableBackToTop\"\n\t\t:loadmoreoffset=\"lowerThreshold\"\n\t\t:showScrollbar=\"showScrollbar\"\n\t\t:style=\"[listStyle]\"\n\t\t:offset-accuracy=\"Number(offsetAccuracy)\"\n\t\t@scroll=\"onScroll\"\n\t\t@loadmore=\"scrolltolower\"\n\t>\n\t\t<slot />\n\t</list>\n\t<!-- #endif -->\n\t<!-- #ifndef APP-NVUE -->\n\t<scroll-view\n\t\tclass=\"u-list\"\n\t\t:scroll-into-view=\"scrollIntoView\"\n\t\t:style=\"[listStyle]\"\n\t\tscroll-y\n\t\t:scroll-top=\"Number(scrollTop)\"\n\t\t:lower-threshold=\"Number(lowerThreshold)\"\n\t\t:upper-threshold=\"Number(upperThreshold)\"\n\t\t:show-scrollbar=\"showScrollbar\"\n\t\t:enable-back-to-top=\"enableBackToTop\"\n\t\t:scroll-with-animation=\"scrollWithAnimation\"\n\t\t@scroll=\"onScroll\"\n\t\t@scrolltolower=\"scrolltolower\"\n\t\t@scrolltoupper=\"scrolltoupper\"\n\t>\n\t\t<view>\n\t\t\t<slot />\n\t\t</view>\n\t</scroll-view>\n\t<!-- #endif -->\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * List 列表\n\t * @description 该组件为高性能列表组件\n\t * @tutorial https://www.uviewui.com/components/list.html\n\t * @property {Boolean}\t\t\tshowScrollbar\t\t控制是否出现滚动条，仅nvue有效 （默认 false ）\n\t * @property {String ｜ Number}\tlowerThreshold\t\t距底部多少时触发scrolltolower事件 （默认 50 ）\n\t * @property {String ｜ Number}\tupperThreshold\t\t距顶部多少时触发scrolltoupper事件，非nvue有效 （默认 0 ）\n\t * @property {String ｜ Number}\tscrollTop\t\t\t设置竖向滚动条位置（默认 0 ）\n\t * @property {String ｜ Number}\toffsetAccuracy\t\t控制 onscroll 事件触发的频率，仅nvue有效（默认 10 ）\n\t * @property {Boolean}\t\t\tenableFlex\t\t\t启用 flexbox 布局。开启后，当前节点声明了display: flex就会成为flex container，并作用于其孩子节点，仅微信小程序有效（默认 false ）\n\t * @property {Boolean}\t\t\tpagingEnabled\t\t是否按分页模式显示List，（默认 false ）\n\t * @property {Boolean}\t\t\tscrollable\t\t\t是否允许List滚动（默认 true ）\n\t * @property {String}\t\t\tscrollIntoView\t\t值应为某子元素id（id不能以数字开头）\n\t * @property {Boolean}\t\t\tscrollWithAnimation\t在设置滚动条位置时使用动画过渡 （默认 false ）\n\t * @property {Boolean}\t\t\tenableBackToTop\t\tiOS点击顶部状态栏、安卓双击标题栏时，滚动条返回顶部，只对微信小程序有效 （默认 false ）\n\t * @property {String ｜ Number}\theight\t\t\t\t列表的高度 （默认 0 ）\n\t * @property {String ｜ Number}\twidth\t\t\t\t列表宽度 （默认 0 ）\n\t * @property {String ｜ Number}\tpreLoadScreen\t\t列表前后预渲染的屏数，1代表一个屏幕的高度，1.5代表1个半屏幕高度  （默认 1 ）\n\t * @property {Object}\t\t\tcustomStyle\t\t\t定义需要用到的外部样式\n\t *\n\t * @example <u-list @scrolltolower=\"scrolltolower\"></u-list>\n\t */\n\texport default {\n\t\tname: 'u-list',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\twatch: {\n\t\t\tscrollIntoView(n) {\n\t\t\t\tthis.scrollIntoViewById(n)\n\t\t\t}\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 记录内部滚动的距离\n\t\t\t\tinnerScrollTop: 0,\n\t\t\t\t// vue下，scroll-view在上拉加载时的偏移值\n\t\t\t\toffset: 0,\n\t\t\t\tsys: uni.$u.sys()\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tlistStyle() {\n\t\t\t\tconst style = {},\n\t\t\t\t\taddUnit = uni.$u.addUnit\n\t\t\t\tif (this.width != 0) style.width = addUnit(this.width)\n\t\t\t\tif (this.height != 0) style.height = addUnit(this.height)\n\t\t\t\t// 如果没有定义列表高度，则默认使用屏幕高度\n\t\t\t\tif (!style.height) style.height = addUnit(this.sys.windowHeight, 'px')\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\tprovide() {\n\t\t\treturn {\n\t\t\t\tuList: this\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.refs = []\n\t\t\tthis.children = []\n\t\t\tthis.anchors = []\n\t\t},\n\t\tmounted() {},\n\t\tmethods: {\n\t\t\tupdateOffsetFromChild(top) {\n\t\t\t\tthis.offset = top\n\t\t\t},\n\t\t\tonScroll(e) {\n\t\t\t\tlet scrollTop = 0\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tscrollTop = e.contentOffset.y\n\t\t\t\t// #endif\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tscrollTop = e.detail.scrollTop\n\t\t\t\t// #endif\n\t\t\t\tthis.innerScrollTop = scrollTop\n\t\t\t\tthis.$emit('scroll', Math.abs(scrollTop))\n\t\t\t},\n\t\t\tscrollIntoViewById(id) {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// 根据id参数，找到所有u-list-item中匹配的节点，再通过dom模块滚动到对应的位置\n\t\t\t\tconst item = this.refs.find(item => item.$refs[id] ? true : false)\n\t\t\t\tdom.scrollToElement(item.$refs[id], {\n\t\t\t\t\t// 是否需要滚动动画\n\t\t\t\t\tanimated: this.scrollWithAnimation\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 滚动到底部触发事件\n\t\t\tscrolltolower(e) {\n\t\t\t\tuni.$u.sleep(30).then(() => {\n\t\t\t\t\tthis.$emit('scrolltolower')\n\t\t\t\t})\n\t\t\t},\n\t\t\t// #ifndef APP-NVUE\n\t\t\t// 滚动到底部时触发，非nvue有效\n\t\t\tscrolltoupper(e) {\n\t\t\t\tuni.$u.sleep(30).then(() => {\n\t\t\t\t\tthis.$emit('scrolltoupper')\n\t\t\t\t\t// 这一句很重要，能绝对保证在性功能障碍的webview，滚动条到顶时，取消偏移值，让页面置顶\n\t\t\t\t\tthis.offset = 0\n\t\t\t\t})\n\t\t\t}\n\t\t\t// #endif\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-list {\n\t\t@include flex(column);\n\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-list-item/props.js",
    "content": "export default {\n    props: {\n        // 用于滚动到指定item\n        anchor: {\n            type: [String, Number],\n            default: uni.$u.props.listItem.anchor\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-list-item/u-list-item.vue",
    "content": "<template>\n\t<!-- #ifdef APP-NVUE -->\n\t<cell>\n\t\t<!-- #endif -->\n\t\t<view\n\t\t\tclass=\"u-list-item\"\n\t\t\t:ref=\"`u-list-item-${anchor}`\"\n\t\t\t:anchor=\"`u-list-item-${anchor}`\"\n\t\t\t:class=\"[`u-list-item-${anchor}`]\"\n\t\t>\n\t\t\t<slot />\n\t\t</view>\n\t\t<!-- #ifdef APP-NVUE -->\n\t</cell>\n\t<!-- #endif -->\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * List 列表\n\t * @description 该组件为高性能列表组件\n\t * @tutorial https://www.uviewui.com/components/list.html\n\t * @property {String | Number}\tanchor\t用于滚动到指定item\n\t * @example <u-list-ite v-for=\"(item, index) in indexList\" :key=\"index\" ></u-list-item>\n\t */\n\texport default {\n\t\tname: 'u-list-item',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 节点信息\n\t\t\t\trect: {},\n\t\t\t\tindex: 0,\n\t\t\t\tshow: true,\n\t\t\t\tsys: uni.$u.sys()\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\n\t\t},\n\t\tinject: ['uList'],\n\t\twatch: {\n\t\t\t// #ifndef APP-NVUE\n\t\t\t'uList.innerScrollTop'(n) {\n\t\t\t\tconst preLoadScreen = this.uList.preLoadScreen\n\t\t\t\tconst windowHeight = this.sys.windowHeight\n\t\t\t\tif(n <= windowHeight * preLoadScreen) {\n\t\t\t\t\tthis.parent.updateOffsetFromChild(0)\n\t\t\t\t} else if (this.rect.top <= n - windowHeight * preLoadScreen) {\n\t\t\t\t\tthis.parent.updateOffsetFromChild(this.rect.top)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// #endif\n\t\t},\n\t\tcreated() {\n\t\t\tthis.parent = {}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 初始化数据\n\t\t\t\tthis.updateParentData()\n\t\t\t\tthis.index = this.parent.children.indexOf(this)\n\t\t\t\tthis.resize()\n\t\t\t},\n\t\t\tupdateParentData() {\n\t\t\t\t// 此方法在mixin中\n\t\t\t\tthis.getParentData('u-list')\n\t\t\t},\n\t\t\tresize() {\n\t\t\t\tthis.queryRect(`u-list-item-${this.anchor}`).then(size => {\n\t\t\t\t\tconst lastChild = this.parent.children[this.index - 1]\n\t\t\t\t\tthis.rect = size\n\t\t\t\t\tconst preLoadScreen = this.uList.preLoadScreen\n\t\t\t\t\tconst windowHeight = this.sys.windowHeight\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\tif (lastChild) {\n\t\t\t\t\t\tthis.rect.top = lastChild.rect.top + lastChild.rect.height\n\t\t\t\t\t}\n\t\t\t\t\tif (size.top >= this.uList.innerScrollTop + (1 + preLoadScreen) * windowHeight) this.show =\n\t\t\t\t\t\tfalse\n\t\t\t\t\t// #endif\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 查询元素尺寸\n\t\t\tqueryRect(el) {\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\tthis.$uGetRect(`.${el}`).then(size => {\n\t\t\t\t\t\tresolve(size)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\tconst ref = this.$refs[el]\n\t\t\t\t\tdom.getComponentRect(ref, res => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-list-item {}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-loading-icon/props.js",
    "content": "export default {\n    props: {\n        // 是否显示组件\n        show: {\n            type: Boolean,\n            default: uni.$u.props.loadingIcon.show\n        },\n        // 颜色\n        color: {\n            type: String,\n            default: uni.$u.props.loadingIcon.color\n        },\n        // 提示文字颜色\n        textColor: {\n            type: String,\n            default: uni.$u.props.loadingIcon.textColor\n        },\n        // 文字和图标是否垂直排列\n        vertical: {\n            type: Boolean,\n            default: uni.$u.props.loadingIcon.vertical\n        },\n        // 模式选择，circle-圆形，spinner-花朵形，semicircle-半圆形\n        mode: {\n            type: String,\n            default: uni.$u.props.loadingIcon.mode\n        },\n        // 图标大小，单位默认px\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.loadingIcon.size\n        },\n        // 文字大小\n        textSize: {\n            type: [String, Number],\n            default: uni.$u.props.loadingIcon.textSize\n        },\n        // 文字内容\n        text: {\n            type: [String, Number],\n            default: uni.$u.props.loadingIcon.text\n        },\n        // 动画模式\n        timingFunction: {\n            type: String,\n            default: uni.$u.props.loadingIcon.timingFunction\n        },\n        // 动画执行周期时间\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.loadingIcon.duration\n        },\n        // mode=circle时的暗边颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.loadingIcon.inactiveColor\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-loading-icon/u-loading-icon.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-loading-icon\"\n\t\t:style=\"[$u.addStyle(customStyle)]\"\n\t\t:class=\"[vertical && 'u-loading-icon--vertical']\"\n\t\tv-if=\"show\"\n\t>\n\t\t<view\n\t\t\tv-if=\"!webviewHide\"\n\t\t\tclass=\"u-loading-icon__spinner\"\n\t\t\t:class=\"[`u-loading-icon__spinner--${mode}`]\"\n\t\t\tref=\"ani\"\n\t\t\t:style=\"{\n\t\t\t\tcolor: color,\n\t\t\t\twidth: $u.addUnit(size),\n\t\t\t\theight: $u.addUnit(size),\n\t\t\t\tborderTopColor: color,\n\t\t\t\tborderBottomColor: otherBorderColor,\n\t\t\t\tborderLeftColor: otherBorderColor,\n\t\t\t\tborderRightColor: otherBorderColor,\n\t\t\t\t'animation-duration': `${duration}ms`,\n\t\t\t\t'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''\n\t\t\t}\"\n\t\t>\n\t\t\t<block v-if=\"mode === 'spinner'\">\n\t\t\t\t<!-- #ifndef APP-NVUE -->\n\t\t\t\t<view\n\t\t\t\t\tv-for=\"(item, index) in array12\"\n\t\t\t\t\t:key=\"index\"\n\t\t\t\t\tclass=\"u-loading-icon__dot\"\n\t\t\t\t>\n\t\t\t\t</view>\n\t\t\t\t<!-- #endif -->\n\t\t\t\t<!-- #ifdef APP-NVUE -->\n\t\t\t\t<!-- 此组件内部图标部分无法设置宽高，即使通过width和height配置了也无效 -->\n\t\t\t\t<loading-indicator\n\t\t\t\t\tv-if=\"!webviewHide\"\n\t\t\t\t\tclass=\"u-loading-indicator\"\n\t\t\t\t\t:animating=\"true\"\n\t\t\t\t\t:style=\"{\n\t\t\t\t\t\tcolor: color,\n\t\t\t\t\t\twidth: $u.addUnit(size),\n\t\t\t\t\t\theight: $u.addUnit(size)\n\t\t\t\t\t}\"\n\t\t\t\t/>\n\t\t\t\t<!-- #endif -->\n\t\t\t</block>\n\t\t</view>\n\t\t<text\n\t\t\tv-if=\"text\"\n\t\t\tclass=\"u-loading-icon__text\"\n\t\t\t:style=\"{\n\t\t\t\tfontSize: $u.addUnit(textSize),\n\t\t\t\tcolor: textColor,\n\t\t\t}\"\n\t\t>{{text}}</text>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst animation = weex.requireModule('animation');\n\t// #endif\n\t/**\n\t * loading 加载动画\n\t * @description 警此组件为一个小动画，目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。\n\t * @tutorial https://www.uviewui.com/components/loading.html\n\t * @property {Boolean}\t\t\tshow\t\t\t是否显示组件  (默认 true)\n\t * @property {String}\t\t\tcolor\t\t\t动画活动区域的颜色，只对 mode = flower 模式有效（默认color['u-tips-color']）\n\t * @property {String}\t\t\ttextColor\t\t提示文本的颜色（默认color['u-tips-color']）\n\t * @property {Boolean}\t\t\tvertical\t\t文字和图标是否垂直排列 (默认 false )\n\t * @property {String}\t\t\tmode\t\t\t模式选择，见官网说明（默认 'circle' ）\n\t * @property {String | Number}\tsize\t\t\t加载图标的大小，单位px （默认 24 ）\n\t * @property {String | Number}\ttextSize\t\t文字大小（默认 15 ）\n\t * @property {String | Number}\ttext\t\t\t文字内容 \n\t * @property {String}\t\t\ttimingFunction\t动画模式 （默认 'ease-in-out' ）\n\t * @property {String | Number}\tduration\t\t动画执行周期时间（默认 1200）\n\t * @property {String}\t\t\tinactiveColor\tmode=circle时的暗边颜色 \n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\n\t * @example <u-loading mode=\"circle\"></u-loading>\n\t */\n\texport default {\n\t\tname: 'u-loading-icon',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// Array.form可以通过一个伪数组对象创建指定长度的数组\n\t\t\t\t// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from\n\t\t\t\tarray12: Array.from({\n\t\t\t\t\tlength: 12\n\t\t\t\t}),\n\t\t\t\t// 这里需要设置默认值为360，否则在安卓nvue上，会延迟一个duration周期后才执行\n\t\t\t\t// 在iOS nvue上，则会一开始默认执行两个周期的动画\n\t\t\t\taniAngel: 360, // 动画旋转角度\n\t\t\t\twebviewHide: false, // 监听webview的状态，如果隐藏了页面，则停止动画，以免性能消耗\n\t\t\t\tloading: false, // 是否运行中，针对nvue使用\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 当为circle类型时，给其另外三边设置一个更轻一些的颜色\n\t\t\t// 之所以需要这么做的原因是，比如父组件传了color为红色，那么需要另外的三个边为浅红色\n\t\t\t// 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝，导致效果没有那么细腻良好)\n\t\t\totherBorderColor() {\n\t\t\t\tconst lightColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[80]\n\t\t\t\tif (this.mode === 'circle') {\n\t\t\t\t\treturn this.inactiveColor ? this.inactiveColor : lightColor\n\t\t\t\t} else {\n\t\t\t\t\treturn 'transparent'\n\t\t\t\t}\n\t\t\t\t// return this.mode === 'circle' ? this.inactiveColor ? this.inactiveColor : lightColor : 'transparent'\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tshow(n) {\n\t\t\t\t// nvue中，show为true，且为非loading状态，就重新执行动画模块\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tif (n && !this.loading) {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tthis.startAnimate()\n\t\t\t\t\t}, 30)\n\t\t\t\t}\n\t\t\t\t// #endif\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\tthis.show && this.nvueAnimate()\n\t\t\t\t\t// #endif\n\t\t\t\t\t// #ifdef APP-PLUS \n\t\t\t\t\tthis.show && this.addEventListenerToWebview()\n\t\t\t\t\t// #endif\n\t\t\t\t}, 20)\n\t\t\t},\n\t\t\t// 监听webview的显示与隐藏\n\t\t\taddEventListenerToWebview() {\n\t\t\t\t// webview的堆栈\n\t\t\t\tconst pages = getCurrentPages()\n\t\t\t\t// 当前页面\n\t\t\t\tconst page = pages[pages.length - 1]\n\t\t\t\t// 当前页面的webview实例\n\t\t\t\tconst currentWebview = page.$getAppWebview()\n\t\t\t\t// 监听webview的显示与隐藏，从而停止或者开始动画(为了性能)\n\t\t\t\tcurrentWebview.addEventListener('hide', () => {\n\t\t\t\t\tthis.webviewHide = true\n\t\t\t\t})\n\t\t\t\tcurrentWebview.addEventListener('show', () => {\n\t\t\t\t\tthis.webviewHide = false\n\t\t\t\t})\n\t\t\t},\n\t\t\t// #ifdef APP-NVUE\n\t\t\tnvueAnimate() {\n\t\t\t\t// nvue下，非spinner类型时才需要旋转，因为nvue的spinner类型，使用了weex的\n\t\t\t\t// loading-indicator组件，自带旋转功能\n\t\t\t\tthis.mode !== 'spinner' && this.startAnimate()\n\t\t\t},\n\t\t\t// 执行nvue的animate模块动画\n\t\t\tstartAnimate() {\n\t\t\t\tthis.loading = true\n\t\t\t\tconst ani = this.$refs.ani\n\t\t\t\tif (!ani) return\n\t\t\t\tanimation.transition(ani, {\n\t\t\t\t\t// 进行角度旋转\n\t\t\t\t\tstyles: {\n\t\t\t\t\t\ttransform: `rotate(${this.aniAngel}deg)`,\n\t\t\t\t\t\ttransformOrigin: 'center center'\n\t\t\t\t\t},\n\t\t\t\t\tduration: this.duration,\n\t\t\t\t\ttimingFunction: this.timingFunction,\n\t\t\t\t\t// delay: 10\n\t\t\t\t}, () => {\n\t\t\t\t\t// 每次增加360deg，为了让其重新旋转一周\n\t\t\t\t\tthis.aniAngel += 360\n\t\t\t\t\t// 动画结束后，继续循环执行动画，需要同时判断webviewHide变量\n\t\t\t\t\t// nvue安卓，页面隐藏后依然会继续执行startAnimate方法\n\t\t\t\t\tthis.show && !this.webviewHide ? this.startAnimate() : this.loading = false\n\t\t\t\t})\n\t\t\t}\n\t\t\t// #endif\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-loading-icon-color: #c8c9cc !default;\n\t$u-loading-icon-text-margin-left:4px !default;\n\t$u-loading-icon-text-color:$u-content-color !default;\n\t$u-loading-icon-text-font-size:14px !default;\n\t$u-loading-icon-text-line-height:20px !default;\n\t$u-loading-width:30px !default;\n\t$u-loading-height:30px !default;\n\t$u-loading-max-width:100% !default;\n\t$u-loading-max-height:100% !default;\n\t$u-loading-semicircle-border-width: 2px !default;\n\t$u-loading-semicircle-border-color:transparent !default;\n\t$u-loading-semicircle-border-top-right-radius: 100px !default;\n\t$u-loading-semicircle-border-top-left-radius: 100px !default;\n\t$u-loading-semicircle-border-bottom-left-radius: 100px !default;\n\t$u-loading-semicircle-border-bottom-right-radiu: 100px !default;\n\t$u-loading-semicircle-border-style: solid !default;\n\t$u-loading-circle-border-top-right-radius: 100px !default;\n\t$u-loading-circle-border-top-left-radius: 100px !default;\n\t$u-loading-circle-border-bottom-left-radius: 100px !default;\n\t$u-loading-circle-border-bottom-right-radiu: 100px !default;\n\t$u-loading-circle-border-width:2px !default;\n\t$u-loading-circle-border-top-color:#e5e5e5 !default;\n\t$u-loading-circle-border-right-color:$u-loading-circle-border-top-color !default;\n\t$u-loading-circle-border-bottom-color:$u-loading-circle-border-top-color !default;\n\t$u-loading-circle-border-left-color:$u-loading-circle-border-top-color !default;\n\t$u-loading-circle-border-style:solid !default;\n\t$u-loading-icon-host-font-size:0px !default;\n\t$u-loading-icon-host-line-height:1 !default;\n\t$u-loading-icon-vertical-margin:6px 0 0 !default;\n\t$u-loading-icon-dot-top:0 !default;\n\t$u-loading-icon-dot-left:0 !default;\n\t$u-loading-icon-dot-width:100% !default;\n\t$u-loading-icon-dot-height:100% !default;\n\t$u-loading-icon-dot-before-width:2px !default;\n\t$u-loading-icon-dot-before-height:25% !default;\n\t$u-loading-icon-dot-before-margin:0 auto !default;\n\t$u-loading-icon-dot-before-background-color:currentColor !default;\n\t$u-loading-icon-dot-before-border-radius:40% !default;\n\n\t.u-loading-icon {\n\t\t/* #ifndef APP-NVUE */\n\t\t// display: inline-flex;\n\t\t/* #endif */\n\t\tflex-direction: row;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tcolor: $u-loading-icon-color;\n\n\t\t&__text {\n\t\t\tmargin-left: $u-loading-icon-text-margin-left;\n\t\t\tcolor: $u-loading-icon-text-color;\n\t\t\tfont-size: $u-loading-icon-text-font-size;\n\t\t\tline-height: $u-loading-icon-text-line-height;\n\t\t}\n\n\t\t&__spinner {\n\t\t\twidth: $u-loading-width;\n\t\t\theight: $u-loading-height;\n\t\t\tposition: relative;\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tbox-sizing: border-box;\n\t\t\tmax-width: $u-loading-max-width;\n\t\t\tmax-height: $u-loading-max-height;\n\t\t\tanimation: u-rotate 1s linear infinite;\n\t\t\t/* #endif */\n\t\t}\n\n\t\t&__spinner--semicircle {\n\t\t\tborder-width: $u-loading-semicircle-border-width;\n\t\t\tborder-color: $u-loading-semicircle-border-color;\n\t\t\tborder-top-right-radius: $u-loading-semicircle-border-top-right-radius;\n\t\t\tborder-top-left-radius: $u-loading-semicircle-border-top-left-radius;\n\t\t\tborder-bottom-left-radius: $u-loading-semicircle-border-bottom-left-radius;\n\t\t\tborder-bottom-right-radius: $u-loading-semicircle-border-bottom-right-radiu;\n\t\t\tborder-style: $u-loading-semicircle-border-style;\n\t\t}\n\n\t\t&__spinner--circle {\n\t\t\tborder-top-right-radius: $u-loading-circle-border-top-right-radius;\n\t\t\tborder-top-left-radius: $u-loading-circle-border-top-left-radius;\n\t\t\tborder-bottom-left-radius: $u-loading-circle-border-bottom-left-radius;\n\t\t\tborder-bottom-right-radius: $u-loading-circle-border-bottom-right-radiu;\n\t\t\tborder-width: $u-loading-circle-border-width;\n\t\t\tborder-top-color: $u-loading-circle-border-top-color;\n\t\t\tborder-right-color: $u-loading-circle-border-right-color;\n\t\t\tborder-bottom-color: $u-loading-circle-border-bottom-color;\n\t\t\tborder-left-color: $u-loading-circle-border-left-color;\n\t\t\tborder-style: $u-loading-circle-border-style;\n\t\t}\n\n\t\t&--vertical {\n\t\t\tflex-direction: column\n\t\t}\n\t}\n\n\t/* #ifndef APP-NVUE */\n\t:host {\n\t\tfont-size: $u-loading-icon-host-font-size;\n\t\tline-height: $u-loading-icon-host-line-height;\n\t}\n\n\t.u-loading-icon {\n\t\t&__spinner--spinner {\n\t\t\tanimation-timing-function: steps(12)\n\t\t}\n\n\t\t&__text:empty {\n\t\t\tdisplay: none\n\t\t}\n\n\t\t&--vertical &__text {\n\t\t\tmargin: $u-loading-icon-vertical-margin;\n\t\t\tcolor: $u-content-color;\n\t\t}\n\n\t\t&__dot {\n\t\t\tposition: absolute;\n\t\t\ttop: $u-loading-icon-dot-top;\n\t\t\tleft: $u-loading-icon-dot-left;\n\t\t\twidth: $u-loading-icon-dot-width;\n\t\t\theight: $u-loading-icon-dot-height;\n\n\t\t\t&:before {\n\t\t\t\tdisplay: block;\n\t\t\t\twidth: $u-loading-icon-dot-before-width;\n\t\t\t\theight: $u-loading-icon-dot-before-height;\n\t\t\t\tmargin: $u-loading-icon-dot-before-margin;\n\t\t\t\tbackground-color: $u-loading-icon-dot-before-background-color;\n\t\t\t\tborder-radius: $u-loading-icon-dot-before-border-radius;\n\t\t\t\tcontent: \" \"\n\t\t\t}\n\t\t}\n\t}\n\n\t@for $i from 1 through 12 {\n\t\t.u-loading-icon__dot:nth-of-type(#{$i}) {\n\t\t\ttransform: rotate($i * 30deg);\n\t\t\topacity: 1 - 0.0625 * ($i - 1);\n\t\t}\n\t}\n\n\t@keyframes u-rotate {\n\t\t0% {\n\t\t\ttransform: rotate(0deg)\n\t\t}\n\n\t\tto {\n\t\t\ttransform: rotate(1turn)\n\t\t}\n\t}\n\n\t/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-loading-page/props.js",
    "content": "export default {\n    props: {\n        // 提示内容\n        loadingText: {\n            type: [String, Number],\n            default: uni.$u.props.loadingPage.loadingText\n        },\n        // 文字上方用于替换loading动画的图片\n        image: {\n            type: String,\n            default: uni.$u.props.loadingPage.image\n        },\n        // 加载动画的模式，circle-圆形，spinner-花朵形，semicircle-半圆形\n        loadingMode: {\n            type: String,\n            default: uni.$u.props.loadingPage.loadingMode\n        },\n        // 是否加载中\n        loading: {\n            type: Boolean,\n            default: uni.$u.props.loadingPage.loading\n        },\n        // 背景色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.loadingPage.bgColor\n        },\n        // 文字颜色\n        color: {\n            type: String,\n            default: uni.$u.props.loadingPage.color\n        },\n        // 文字大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.loadingPage.fontSize\n        },\n\t\t// 图标大小\n\t\ticonSize: {\n\t\t    type: [String, Number],\n\t\t    default: uni.$u.props.loadingPage.fontSize\n\t\t},\n        // 加载中图标的颜色，只能rgb或者十六进制颜色值\n        loadingColor: {\n            type: String,\n            default: uni.$u.props.loadingPage.loadingColor\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-loading-page/u-loading-page.vue",
    "content": "<template>\n    <u-transition\n        :show=\"loading\"\n        :custom-style=\"{\n            position: 'fixed',\n            top: 0,\n            left: 0,\n            right: 0,\n            bottom: 0,\n            backgroundColor: bgColor,\n            display: 'flex',\n        }\"\n    >\n        <view class=\"u-loading-page\">\n            <view class=\"u-loading-page__warpper\">\n                <view class=\"u-loading-page__warpper__loading-icon\">\n                    <image\n                        v-if=\"image\"\n                        :src=\"image\"\n                        class=\"u-loading-page__warpper__loading-icon__img\"\n                        mode=\"widthFit\"\n\t\t\t\t\t\t:style=\"{\n\t\t\t\t\t\t\twidth: $u.addUnit(iconSize),\n\t\t\t\t\t\t    height: $u.addUnit(iconSize)\n\t\t\t\t\t\t}\"\n                    ></image>\n                    <u-loading-icon\n                        v-else\n                        :mode=\"loadingMode\"\n                        :size=\"$u.addUnit(iconSize)\"\n                        :color=\"loadingColor\"\n                    ></u-loading-icon>\n                </view>\n                <slot>\n                    <text\n                        class=\"u-loading-page__warpper__text\"\n                        :style=\"{\n                            fontSize: $u.addUnit(fontSize),\n                            color: color,\n                        }\"\n                        >{{ loadingText }}</text\n                    >\n                </slot>\n            </view>\n        </view>\n    </u-transition>\n</template>\n\n<script>\nimport props from \"./props.js\";\n/**\n * loadingPage 加载动画\n * @description 警此组件为一个小动画，目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。\n * @tutorial https://www.uviewui.com/components/loading.html\n * @property {String | Number}\tloadingText\t\t提示内容  (默认 '正在加载' )\n * @property {String}\t\t\timage\t\t\t文字上方用于替换loading动画的图片\n * @property {String}\t\t\tloadingMode\t\t加载动画的模式，circle-圆形，spinner-花朵形，semicircle-半圆形 （默认 'circle' ）\n * @property {Boolean}\t\t\tloading\t\t\t是否加载中 （默认 false ）\n * @property {String}\t\t\tbgColor\t\t\t背景色 （默认 '#ffffff' ）\n * @property {String}\t\t\tcolor\t\t\t文字颜色 （默认 '#C8C8C8' ）\n * @property {String | Number}\tfontSize\t\t文字大小 （默认 19 ）\n * @property {String | Number}\ticonSize\t\t图标大小 （默认 28 ）\n * @property {String}\t\t\tloadingColor\t加载中图标的颜色，只能rgb或者十六进制颜色值 （默认 '#C8C8C8' ）\n * @property {Object}\t\t\tcustomStyle\t\t自定义样式\n * @example <u-loading mode=\"circle\"></u-loading>\n */\nexport default {\n    name: \"u-loading-page\",\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n    data() {\n        return {};\n    },\n    methods: {},\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n\n$text-color: rgb(200, 200, 200) !default;\n$text-size: 19px !default;\n$u-loading-icon-margin-bottom: 10px !default;\n\n.u-loading-page {\n    @include flex(column);\n    flex: 1;\n    align-items: center;\n    justify-content: center;\n\n    &__warpper {\n        margin-top: -150px;\n        justify-content: center;\n        align-items: center;\n        /* #ifndef APP-NVUE */\n        color: $text-color;\n        font-size: $text-size;\n        /* #endif */\n        @include flex(column);\n\n        &__loading-icon {\n            margin-bottom: $u-loading-icon-margin-bottom;\n\n            &__img {\n                width: 40px;\n                height: 40px;\n            }\n        }\n\n        &__text {\n            font-size: $text-size;\n            color: $text-color;\n        }\n    }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-loadmore/props.js",
    "content": "export default {\n    props: {\n        // 组件状态，loadmore-加载前的状态，loading-加载中的状态，nomore-没有更多的状态\n        status: {\n            type: String,\n            default: uni.$u.props.loadmore.status\n        },\n        // 组件背景色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.loadmore.bgColor\n        },\n        // 是否显示加载中的图标\n        icon: {\n            type: Boolean,\n            default: uni.$u.props.loadmore.icon\n        },\n        // 字体大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.loadmore.fontSize\n        },\n\t\t    // 图标大小\n        iconSize: {\n            type: [String, Number],\n            default: uni.$u.props.loadmore.iconSize\n        },\n        // 字体颜色\n        color: {\n            type: String,\n            default: uni.$u.props.loadmore.color\n        },\n        // 加载中状态的图标，spinner-花朵状图标，circle-圆圈状，semicircle-半圆\n        loadingIcon: {\n            type: String,\n            default: uni.$u.props.loadmore.loadingIcon\n        },\n        // 加载前的提示语\n        loadmoreText: {\n            type: String,\n            default: uni.$u.props.loadmore.loadmoreText\n        },\n        // 加载中提示语\n        loadingText: {\n            type: String,\n            default: uni.$u.props.loadmore.loadingText\n        },\n        // 没有更多的提示语\n        nomoreText: {\n            type: String,\n            default: uni.$u.props.loadmore.nomoreText\n        },\n        // 在“没有更多”状态下，是否显示粗点\n        isDot: {\n            type: Boolean,\n            default: uni.$u.props.loadmore.isDot\n        },\n        // 加载中图标的颜色\n        iconColor: {\n            type: String,\n            default: uni.$u.props.loadmore.iconColor\n        },\n        // 上边距\n        marginTop: {\n            type: [String, Number],\n            default: uni.$u.props.loadmore.marginTop\n        },\n        // 下边距\n        marginBottom: {\n            type: [String, Number],\n            default: uni.$u.props.loadmore.marginBottom\n        },\n        // 高度，单位px\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.loadmore.height\n        },\n        // 是否显示左边分割线\n        line: {\n            type: Boolean,\n            default: uni.$u.props.loadmore.line\n        },\n        // 线条颜色\n        lineColor: {\n            type: String,\n            default: uni.$u.props.loadmore.lineColor\n        },\n        // 是否虚线，true-虚线，false-实线\n        dashed: {\n            type: Boolean,\n            default: uni.$u.props.loadmore.dashed\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-loadmore/u-loadmore.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-loadmore\"\n\t    :style=\"[\n\t\t\t$u.addStyle(customStyle),\n\t\t\t{\n\t\t\t\tbackgroundColor: bgColor,\n\t\t\t\tmarginBottom: $u.addUnit(marginBottom),\n\t\t\t\tmarginTop: $u.addUnit(marginTop),\n\t\t\t\theight: $u.addUnit(height),\n\t\t\t},\n\t\t]\"\n\t>\n\t\t<u-line\n\t\t    length=\"140rpx\"\n\t\t    :color=\"lineColor\"\n\t\t    :hairline=\"false\"\n\t\t\t:dashed=\"dashed\"\n\t\t\tv-if=\"line\"\n\t\t></u-line>\n\t\t<!-- 加载中和没有更多的状态才显示两边的横线 -->\n\t\t<view\n\t\t    :class=\"status == 'loadmore' || status == 'nomore' ? 'u-more' : ''\"\n\t\t    class=\"u-loadmore__content\"\n\t\t>\n\t\t\t<view\n\t\t\t    class=\"u-loadmore__content__icon-wrap\"\n\t\t\t    v-if=\"status === 'loading' && icon\"\n\t\t\t>\n\t\t\t\t<u-loading-icon\n\t\t\t\t    :color=\"iconColor\"\n\t\t\t\t    :size=\"iconSize\"\n\t\t\t\t    :mode=\"loadingIcon\"\n\t\t\t\t></u-loading-icon>\n\t\t\t</view>\n\t\t\t<!-- 如果没有更多的状态下，显示内容为dot（粗点），加载特定样式 -->\n\t\t\t<text\n\t\t\t    class=\"u-line-1\"\n\t\t\t    :style=\"[loadTextStyle]\"\n\t\t\t    :class=\"[(status == 'nomore' && isDot == true) ? 'u-loadmore__content__dot-text' : 'u-loadmore__content__text']\"\n\t\t\t    @tap=\"loadMore\"\n\t\t\t>{{ showText }}</text>\n\t\t</view>\n\t\t<u-line\n\t\t    length=\"140rpx\"\n\t\t    :color=\"lineColor\"\n\t\t\t:hairline=\"false\"\n\t\t\t:dashed=\"dashed\"\n\t\t\tv-if=\"line\"\n\t\t></u-line>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * loadmore 加载更多\n\t * @description 此组件一般用于标识页面底部加载数据时的状态。\n\t * @tutorial https://www.uviewui.com/components/loadMore.html\n\t * @property {String}\t\t\tstatus\t\t\t组件状态（默认 'loadmore' ）\n\t * @property {String}\t\t\tbgColor\t\t\t组件背景颜色，在页面是非白色时会用到（默认 'transparent' ）\n\t * @property {Boolean}\t\t\ticon\t\t\t加载中时是否显示图标（默认 true ）\n\t * @property {String | Number}\tfontSize\t\t字体大小（默认 14 ）\n\t * @property {String | Number}\ticonSize\t\t图标大小（默认 17 ）\n\t * @property {String}\t\t\tcolor\t\t\t字体颜色（默认 '#606266' ）\n\t * @property {String}\t\t\tloadingIcon\t\t加载图标（默认 'circle' ）\n\t * @property {String}\t\t\tloadmoreText\t加载前的提示语（默认 '加载更多' ）\n\t * @property {String}\t\t\tloadingText\t\t加载中提示语（默认 '正在加载...' ）\n\t * @property {String}\t\t\tnomoreText\t\t没有更多的提示语（默认 '没有更多了' ）\n\t * @property {Boolean}\t\t\tisDot\t\t\t到上一个相邻元素的距离 （默认 false ）\n\t * @property {String}\t\t\ticonColor\t\t加载中图标的颜色 （默认 '#b7b7b7' ）\n\t * @property {String}\t\t\tlineColor\t\t线条颜色（默认 #E6E8EB ）\n\t * @property {String | Number}\tmarginTop\t\t上边距 （默认 10 ）\n\t * @property {String | Number}\tmarginBottom\t下边距 （默认 10 ）\n\t * @property {String | Number}\theight\t\t\t高度，单位px （默认 'auto' ）\n\t * @property {Boolean}\t\t\tline\t\t\t是否显示左边分割线  （默认 false ）\n\t * @property {Boolean}\t\t\tdashed\t\t// 是否虚线，true-虚线，false-实线  （默认 false ）\n\t * @event {Function} loadmore status为loadmore时，点击组件会发出此事件\n\t * @example <u-loadmore :status=\"status\" icon-type=\"iconType\" load-text=\"loadText\" />\n\t */\n\texport default {\n\t\tname: \"u-loadmore\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 粗点\n\t\t\t\tdotText: \"●\"\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 加载的文字显示的样式\n\t\t\tloadTextStyle() {\n\t\t\t\treturn {\n\t\t\t\t\tcolor: this.color,\n\t\t\t\t\tfontSize: uni.$u.addUnit(this.fontSize),\n\t\t\t\t\tlineHeight: uni.$u.addUnit(this.fontSize),\n\t\t\t\t\tbackgroundColor: this.bgColor,\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 显示的提示文字\n\t\t\tshowText() {\n\t\t\t\tlet text = '';\n\t\t\t\tif (this.status == 'loadmore') text = this.loadmoreText\n\t\t\t\telse if (this.status == 'loading') text = this.loadingText\n\t\t\t\telse if (this.status == 'nomore' && this.isDot) text = this.dotText;\n\t\t\t\telse text = this.nomoreText;\n\t\t\t\treturn text;\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tloadMore() {\n\t\t\t\t// 只有在“加载更多”的状态下才发送点击事件，内容不满一屏时无法触发底部上拉事件，所以需要点击来触发\n\t\t\t\tif (this.status == 'loadmore') this.$emit('loadmore');\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-loadmore {\n\t\t@include flex(row);\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex: 1;\n\n\t\t&__content {\n\t\t\tmargin: 0 15px;\n\t\t\t@include flex(row);\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\n\t\t\t&__icon-wrap {\n\t\t\t\tmargin-right: 8px;\n\t\t\t}\n\n\t\t\t&__text {\n\t\t\t\tfont-size: 14px;\n\t\t\t\tcolor: $u-content-color;\n\t\t\t}\n\n\t\t\t&__dot-text {\n\t\t\t\tfont-size: 15px;\n\t\t\t\tcolor: $u-tips-color;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-modal/props.js",
    "content": "export default {\n    props: {\n        // 是否展示modal\n        show: {\n            type: Boolean,\n            default: uni.$u.props.modal.show\n        },\n        // 标题\n        title: {\n            type: [String],\n            default: uni.$u.props.modal.title\n        },\n        // 弹窗内容\n        content: {\n            type: String,\n            default: uni.$u.props.modal.content\n        },\n        // 确认文案\n        confirmText: {\n            type: String,\n            default: uni.$u.props.modal.confirmText\n        },\n        // 取消文案\n        cancelText: {\n            type: String,\n            default: uni.$u.props.modal.cancelText\n        },\n        // 是否显示确认按钮\n        showConfirmButton: {\n            type: Boolean,\n            default: uni.$u.props.modal.showConfirmButton\n        },\n        // 是否显示取消按钮\n        showCancelButton: {\n            type: Boolean,\n            default: uni.$u.props.modal.showCancelButton\n        },\n        // 确认按钮颜色\n        confirmColor: {\n            type: String,\n            default: uni.$u.props.modal.confirmColor\n        },\n        // 取消文字颜色\n        cancelColor: {\n            type: String,\n            default: uni.$u.props.modal.cancelColor\n        },\n        // 对调确认和取消的位置\n        buttonReverse: {\n            type: Boolean,\n            default: uni.$u.props.modal.buttonReverse\n        },\n        // 是否开启缩放效果\n        zoom: {\n            type: Boolean,\n            default: uni.$u.props.modal.zoom\n        },\n        // 是否异步关闭，只对确定按钮有效\n        asyncClose: {\n            type: Boolean,\n            default: uni.$u.props.modal.asyncClose\n        },\n        // 是否允许点击遮罩关闭modal\n        closeOnClickOverlay: {\n            type: Boolean,\n            default: uni.$u.props.modal.closeOnClickOverlay\n        },\n        // 给一个负的margin-top，往上偏移，避免和键盘重合的情况\n        negativeTop: {\n            type: [String, Number],\n            default: uni.$u.props.modal.negativeTop\n        },\n        // modal宽度，不支持百分比，可以数值，px，rpx单位\n        width: {\n            type: [String, Number],\n            default: uni.$u.props.modal.width\n        },\n        // 确认按钮的样式，circle-圆形，square-方形，如设置，将不会显示取消按钮\n        confirmButtonShape: {\n            type: String,\n            default: uni.$u.props.modal.confirmButtonShape\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-modal/u-modal.vue",
    "content": "<template>\n\t<u-popup\n\t\tmode=\"center\"\n\t\t:zoom=\"zoom\"\n\t\t:show=\"show\"\n\t\t:customStyle=\"{\n\t\t\tborderRadius: '6px', \n\t\t\toverflow: 'hidden',\n\t\t\tmarginTop: `-${$u.addUnit(negativeTop)}`\n\t\t}\"\n\t\t:closeOnClickOverlay=\"closeOnClickOverlay\"\n\t\t:safeAreaInsetBottom=\"false\"\n\t\t:duration=\"400\"\n\t\t@click=\"clickHandler\"\n\t>\n\t\t<view\n\t\t\tclass=\"u-modal\"\n\t\t\t:style=\"{\n\t\t\t\twidth: $u.addUnit(width),\n\t\t\t}\"\n\t\t>\n\t\t\t<text\n\t\t\t\tclass=\"u-modal__title\"\n\t\t\t\tv-if=\"title\"\n\t\t\t>{{ title }}</text>\n\t\t\t<view\n\t\t\t\tclass=\"u-modal__content\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tpaddingTop: `${title ? 12 : 25}px`\n\t\t\t\t}\"\n\t\t\t>\n\t\t\t\t<slot>\n\t\t\t\t\t<text class=\"u-modal__content__text\">{{ content }}</text>\n\t\t\t\t</slot>\n\t\t\t</view>\n\t\t\t<view\n\t\t\t\tclass=\"u-modal__button-group--confirm-button\"\n\t\t\t\tv-if=\"$slots.confirmButton\"\n\t\t\t>\n\t\t\t\t<slot name=\"confirmButton\"></slot>\n\t\t\t</view>\n\t\t\t<template v-else>\n\t\t\t\t<u-line></u-line>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-modal__button-group\"\n\t\t\t\t\t:style=\"{\n\t\t\t\t\t\tflexDirection: buttonReverse ? 'row-reverse' : 'row'\n\t\t\t\t\t}\"\n\t\t\t\t>\n\t\t\t\t\t<view\n\t\t\t\t\t\tclass=\"u-modal__button-group__wrapper u-modal__button-group__wrapper--cancel\"\n\t\t\t\t\t\t:hover-stay-time=\"150\"\n\t\t\t\t\t\thover-class=\"u-modal__button-group__wrapper--hover\"\n\t\t\t\t\t\t:class=\"[showCancelButton && !showConfirmButton && 'u-modal__button-group__wrapper--only-cancel']\"\n\t\t\t\t\t\tv-if=\"showCancelButton\"\n\t\t\t\t\t\t@tap=\"cancelHandler\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tclass=\"u-modal__button-group__wrapper__text\"\n\t\t\t\t\t\t\t:style=\"{\n\t\t\t\t\t\t\t\tcolor: cancelColor\n\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t>{{ cancelText }}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<u-line\n\t\t\t\t\t\tdirection=\"column\"\n\t\t\t\t\t\tv-if=\"showConfirmButton && showCancelButton\"\n\t\t\t\t\t></u-line>\n\t\t\t\t\t<view\n\t\t\t\t\t\tclass=\"u-modal__button-group__wrapper u-modal__button-group__wrapper--confirm\"\n\t\t\t\t\t\t:hover-stay-time=\"150\"\n\t\t\t\t\t\thover-class=\"u-modal__button-group__wrapper--hover\"\n\t\t\t\t\t\t:class=\"[!showCancelButton && showConfirmButton && 'u-modal__button-group__wrapper--only-confirm']\"\n\t\t\t\t\t\tv-if=\"showConfirmButton\"\n\t\t\t\t\t\t@tap=\"confirmHandler\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<u-loading-icon v-if=\"loading\"></u-loading-icon>\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\tclass=\"u-modal__button-group__wrapper__text\"\n\t\t\t\t\t\t\t:style=\"{\n\t\t\t\t\t\t\t\tcolor: confirmColor\n\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t>{{ confirmText }}</text>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</template>\n\t\t</view>\n\t</u-popup>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Modal 模态框\n\t * @description 弹出模态框，常用于消息提示、消息确认、在当前页面内完成特定的交互操作。\n\t * @tutorial https://www.uviewui.com/components/modul.html\n\t * @property {Boolean}\t\t\tshow\t\t\t\t是否显示模态框，请赋值给show （默认 false ）\n\t * @property {String}\t\t\ttitle\t\t\t\t标题内容\n\t * @property {String}\t\t\tcontent\t\t\t\t模态框内容，如传入slot内容，则此参数无效\n\t * @property {String}\t\t\tconfirmText\t\t\t确认按钮的文字 （默认 '确认' ）\n\t * @property {String}\t\t\tcancelText\t\t\t取消按钮的文字 （默认 '取消' ）\n\t * @property {Boolean}\t\t\tshowConfirmButton\t是否显示确认按钮 （默认 true ）\n\t * @property {Boolean}\t\t\tshowCancelButton\t是否显示取消按钮 （默认 false ）\n\t * @property {String}\t\t\tconfirmColor\t\t确认按钮的颜色 （默认 '#2979ff' ）\n\t * @property {String}\t\t\tcancelColor\t\t\t取消按钮的颜色 （默认 '#606266' ）\n\t * @property {Boolean}\t\t\tbuttonReverse\t\t对调确认和取消的位置 （默认 false ）\n\t * @property {Boolean}\t\t\tzoom\t\t\t\t是否开启缩放模式 （默认 true ）\n\t * @property {Boolean}\t\t\tasyncClose\t\t\t是否异步关闭，只对确定按钮有效，见上方说明 （默认 false ）\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t是否允许点击遮罩关闭Modal （默认 false ）\n\t * @property {String | Number}\tnegativeTop\t\t\t往上偏移的值，给一个负的margin-top，往上偏移，避免和键盘重合的情况，单位任意，数值则默认为px单位 （默认 0 ）\n\t * @property {String | Number}\twidth\t\t\t\tmodal宽度，不支持百分比，可以数值，px，rpx单位 （默认 '650rpx' ）\n\t * @property {String}\t\t\tconfirmButtonShape\t确认按钮的样式,如设置，将不会显示取消按钮\n\t * @event {Function} confirm\t点击确认按钮时触发\n\t * @event {Function} cancel\t\t点击取消按钮时触发\n\t * @event {Function} close\t\t点击遮罩关闭出发，closeOnClickOverlay为true有效\n\t * @example <u-loadmore :status=\"status\" icon-type=\"iconType\" load-text=\"loadText\" />\n\t */\n\texport default {\n\t\tname: 'u-modal',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tloading: false\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tshow(n) {\n\t\t\t\t// 为了避免第一次打开modal，又使用了异步关闭的loading\n\t\t\t\t// 第二次打开modal时，loading依然存在的情况\n\t\t\t\tif (n && this.loading) this.loading = false\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击确定按钮\n\t\t\tconfirmHandler() {\n\t\t\t\t// 如果配置了异步关闭，将按钮值为loading状态\n\t\t\t\tif (this.asyncClose) {\n\t\t\t\t\tthis.loading = true;\n\t\t\t\t}\n\t\t\t\tthis.$emit('confirm')\n\t\t\t},\n\t\t\t// 点击取消按钮\n\t\t\tcancelHandler() {\n\t\t\t\tthis.$emit('cancel')\n\t\t\t},\n\t\t\t// 点击遮罩\n\t\t\t// 从原理上来说，modal的遮罩点击，并不是真的点击到了遮罩\n\t\t\t// 因为modal依赖于popup的中部弹窗类型，中部弹窗比较特殊，虽有然遮罩，但是为了让弹窗内容能flex居中\n\t\t\t// 多了一个透明的遮罩，此透明的遮罩会覆盖在灰色的遮罩上，所以实际上是点击不到灰色遮罩的，popup内部在\n\t\t\t// 透明遮罩的子元素做了.stop处理，所以点击内容区，也不会导致误触发\n\t\t\tclickHandler() {\n\t\t\t\tif (this.closeOnClickOverlay) {\n\t\t\t\t\tthis.$emit('close')\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-modal-border-radius: 6px;\n\n\t.u-modal {\n\t\twidth: 650rpx;\n\t\tborder-radius: $u-modal-border-radius;\n\t\toverflow: hidden;\n\n\t\t&__title {\n\t\t\tfont-size: 16px;\n\t\t\tfont-weight: bold;\n\t\t\tcolor: $u-content-color;\n\t\t\ttext-align: center;\n\t\t\tpadding-top: 25px;\n\t\t}\n\n\t\t&__content {\n\t\t\tpadding: 12px 25px 25px 25px;\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\n\t\t\t&__text {\n\t\t\t\tfont-size: 15px;\n\t\t\t\tcolor: $u-content-color;\n\t\t\t\tflex: 1;\n\t\t\t}\n\t\t}\n\n\t\t&__button-group {\n\t\t\t@include flex;\n\n\t\t\t&--confirm-button {\n\t\t\t\tflex-direction: column;\n\t\t\t\tpadding: 0px 25px 15px 25px;\n\t\t\t}\n\n\t\t\t&__wrapper {\n\t\t\t\tflex: 1;\n\t\t\t\t@include flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\theight: 48px;\n\t\t\t\t\n\t\t\t\t&--confirm,\n\t\t\t\t&--only-cancel {\n\t\t\t\t\tborder-bottom-right-radius: $u-modal-border-radius;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t&--cancel,\n\t\t\t\t&--only-confirm {\n\t\t\t\t\tborder-bottom-left-radius: $u-modal-border-radius;\n\t\t\t\t}\n\n\t\t\t\t&--hover {\n\t\t\t\t\tbackground-color: $u-bg-color;\n\t\t\t\t}\n\n\t\t\t\t&__text {\n\t\t\t\t\tcolor: $u-content-color;\n\t\t\t\t\tfont-size: 16px;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-navbar/props.js",
    "content": "export default {\n\tprops: {\n\t\t// 是否开启顶部安全区适配\n\t\tsafeAreaInsetTop: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.navbar.safeAreaInsetTop\n\t\t},\n\t\t// 固定在顶部时，是否生成一个等高元素，以防止塌陷\n\t\tplaceholder: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.navbar.placeholder\n\t\t},\n\t\t// 是否固定在顶部\n\t\tfixed: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.navbar.fixed\n\t\t},\n\t\t// 是否显示下边框\n\t\tborder: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.navbar.border\n\t\t},\n\t\t// 左边的图标\n\t\tleftIcon: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.navbar.leftIcon\n\t\t},\n\t\t// 左边的提示文字\n\t\tleftText: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.navbar.leftText\n\t\t},\n\t\t// 左右的提示文字\n\t\trightText: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.navbar.rightText\n\t\t},\n\t\t// 右边的图标\n\t\trightIcon: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.navbar.rightIcon\n\t\t},\n\t\t// 标题\n\t\ttitle: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.navbar.title\n\t\t},\n\t\t// 背景颜色\n\t\tbgColor: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.navbar.bgColor\n\t\t},\n\t\t// 标题的宽度\n\t\ttitleWidth: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.navbar.titleWidth\n\t\t},\n\t\t// 导航栏高度\n\t\theight: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.navbar.height\n\t\t},\n\t\t// 左侧返回图标的大小\n\t\tleftIconSize: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.navbar.leftIconSize\n\t\t},\n\t\t// 左侧返回图标的颜色\n\t\tleftIconColor: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.navbar.leftIconColor\n\t\t},\n\t\t// 点击左侧区域(返回图标)，是否自动返回上一页\n\t\tautoBack: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.navbar.autoBack\n\t\t},\n\t\t// 标题的样式，对象或字符串\n\t\ttitleStyle: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: uni.$u.props.navbar.titleStyle\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-navbar/u-navbar.vue",
    "content": "<template>\n\t<view class=\"u-navbar\">\n\t\t<view\n\t\t\tclass=\"u-navbar__placeholder\"\n\t\t\tv-if=\"fixed && placeholder\"\n\t\t\t:style=\"{\n\t\t\t\theight: $u.addUnit($u.getPx(height) + $u.sys().statusBarHeight,'px'),\n\t\t\t}\"\n\t\t></view>\n\t\t<view :class=\"[fixed && 'u-navbar--fixed']\">\n\t\t\t<u-status-bar\n\t\t\t\tv-if=\"safeAreaInsetTop\"\n\t\t\t\t:bgColor=\"bgColor\"\n\t\t\t></u-status-bar>\n\t\t\t<view\n\t\t\t\tclass=\"u-navbar__content\"\n\t\t\t\t:class=\"[border && 'u-border-bottom']\"\n\t\t\t\t:style=\"{\n\t\t\t\t\theight: $u.addUnit(height),\n\t\t\t\t\tbackgroundColor: bgColor,\n\t\t\t\t}\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-navbar__content__left\"\n\t\t\t\t\thover-class=\"u-navbar__content__left--hover\"\n\t\t\t\t\thover-start-time=\"150\"\n\t\t\t\t\t@tap=\"leftClick\"\n\t\t\t\t>\n\t\t\t\t\t<slot name=\"left\">\n\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t\tv-if=\"leftIcon\"\n\t\t\t\t\t\t\t:name=\"leftIcon\"\n\t\t\t\t\t\t\t:size=\"leftIconSize\"\n\t\t\t\t\t\t\t:color=\"leftIconColor\"\n\t\t\t\t\t\t></u-icon>\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tv-if=\"leftText\"\n\t\t\t\t\t\t\t:style=\"{\n\t\t\t\t\t\t\t\tcolor: leftIconColor\n\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t\tclass=\"u-navbar__content__left__text\"\n\t\t\t\t\t\t>{{ leftText }}</text>\n\t\t\t\t\t</slot>\n\t\t\t\t</view>\n\t\t\t\t<slot name=\"center\">\n\t\t\t\t\t<text\n\t\t\t\t\t\tclass=\"u-line-1 u-navbar__content__title\"\n\t\t\t\t\t\t:style=\"[{\n\t\t\t\t\t\t\twidth: $u.addUnit(titleWidth),\n\t\t\t\t\t\t}, $u.addStyle(titleStyle)]\"\n\t\t\t\t\t>{{ title }}</text>\n\t\t\t\t</slot>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-navbar__content__right\"\n\t\t\t\t\tv-if=\"$slots.right || rightIcon || rightText\"\n\t\t\t\t\t@tap=\"rightClick\"\n\t\t\t\t>\n\t\t\t\t\t<slot name=\"right\">\n\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t\tv-if=\"rightIcon\"\n\t\t\t\t\t\t\t:name=\"rightIcon\"\n\t\t\t\t\t\t\tsize=\"20\"\n\t\t\t\t\t\t></u-icon>\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t\tv-if=\"rightText\"\n\t\t\t\t\t\t\tclass=\"u-navbar__content__right__text\"\n\t\t\t\t\t\t>{{ rightText }}</text>\n\t\t\t\t\t</slot>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Navbar 自定义导航栏\n\t * @description 此组件一般用于在特殊情况下，需要自定义导航栏的时候用到，一般建议使用uni-app带的导航栏。\n\t * @tutorial https://www.uviewui.com/components/navbar.html\n\t * @property {Boolean}\t\t\tsafeAreaInsetTop\t是否开启顶部安全区适配  （默认 true ）\n\t * @property {Boolean}\t\t\tplaceholder\t\t\t固定在顶部时，是否生成一个等高元素，以防止塌陷 （默认 false ）\n\t * @property {Boolean}\t\t\tfixed\t\t\t\t导航栏是否固定在顶部 （默认 false ）\n\t * @property {Boolean}\t\t\tborder\t\t\t\t导航栏底部是否显示下边框 （默认 false ）\n\t * @property {String}\t\t\tleftIcon\t\t\t左边返回图标的名称，只能为uView自带的图标 （默认 'arrow-left' ）\n\t * @property {String}\t\t\tleftText\t\t\t左边的提示文字\n\t * @property {String}\t\t\trightText\t\t\t右边的提示文字\n\t * @property {String}\t\t\trightIcon\t\t\t右边返回图标的名称，只能为uView自带的图标\n\t * @property {String}\t\t\ttitle\t\t\t\t导航栏标题，如设置为空字符，将会隐藏标题占位区域\n\t * @property {String}\t\t\tbgColor\t\t\t\t导航栏背景设置 （默认 '#ffffff' ）\n\t * @property {String | Number}\ttitleWidth\t\t\t导航栏标题的最大宽度，内容超出会以省略号隐藏 （默认 '400rpx' ）\n\t * @property {String | Number}\theight\t\t\t\t导航栏高度(不包括状态栏高度在内，内部自动加上)（默认 '44px' ）\n\t * @property {String | Number}\tleftIconSize\t\t左侧返回图标的大小（默认 20px ）\n\t * @property {String | Number}\tleftIconColor\t\t左侧返回图标的颜色（默认 #303133 ）\n\t * @property {Boolean}\t        autoBack\t\t\t点击左侧区域(返回图标)，是否自动返回上一页（默认 false ）\n\t * @property {Object | String}\ttitleStyle\t\t\t标题的样式，对象或字符串\n\t * @event {Function} leftClick\t\t点击左侧区域\n\t * @event {Function} rightClick\t\t点击右侧区域\n\t * @example <u-navbar title=\"剑未配妥，出门已是江湖\" left-text=\"返回\" right-text=\"帮助\" @click-left=\"onClickBack\" @click-right=\"onClickRight\"></u-navbar>\n\t */\n\texport default {\n\t\tname: 'u-navbar',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击左侧区域\n\t\t\tleftClick() {\n\t\t\t\t// 如果配置了autoBack，自动返回上一页\n\t\t\t\tthis.$emit('leftClick')\n\t\t\t\tif(this.autoBack) {\n\t\t\t\t\tuni.navigateBack()\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 点击右侧区域\n\t\t\trightClick() {\n\t\t\t\tthis.$emit('rightClick')\n\t\t\t},\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-navbar {\n\n\t\t&--fixed {\n\t\t\tposition: fixed;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t\ttop: 0;\n\t\t\tz-index: 11;\n\t\t}\n\n\t\t&__content {\n\t\t\t@include flex(row);\n\t\t\talign-items: center;\n\t\t\theight: 44px;\n\t\t\tbackground-color: #9acafc;\n\t\t\tposition: relative;\n\t\t\tjustify-content: center;\n\n\t\t\t&__left,\n\t\t\t&__right {\n\t\t\t\tpadding: 0 13px;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\t@include flex(row);\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t&__left {\n\t\t\t\tleft: 0;\n\t\t\t\t\n\t\t\t\t&--hover {\n\t\t\t\t\topacity: 0.7;\n\t\t\t\t}\n\n\t\t\t\t&__text {\n\t\t\t\t\tfont-size: 15px;\n\t\t\t\t\tmargin-left: 3px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&__title {\n\t\t\t\ttext-align: center;\n\t\t\t\tfont-size: 16px;\n\t\t\t\tcolor: $u-main-color;\n\t\t\t}\n\n\t\t\t&__right {\n\t\t\t\tright: 0;\n\n\t\t\t\t&__text {\n\t\t\t\t\tfont-size: 15px;\n\t\t\t\t\tmargin-left: 3px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-no-network/props.js",
    "content": "export default {\n    props: {\n        // 页面文字提示\n        tips: {\n            type: String,\n            default: uni.$u.props.noNetwork.tips\n        },\n        // 一个z-index值，用于设置没有网络这个组件的层次，因为页面可能会有其他定位的元素层级过高，导致此组件被覆盖\n        zIndex: {\n            type: [String, Number],\n            default: uni.$u.props.noNetwork.zIndex\n        },\n        // image 没有网络的图片提示\n        image: {\n            type: String,\n            default: uni.$u.props.noNetwork.image\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-no-network/u-no-network.vue",
    "content": "<template>\n\t<u-overlay\n\t    :show=\"!isConnected\"\n\t\t:zIndex=\"zIndex\"\n\t    @touchmove.stop.prevent=\"noop\"\n\t\t:customStyle=\"{\n\t\t\tbackgroundColor: '#fff',\n\t\t\tdisplay: 'flex',\n\t\t\tjustifyContent: 'center',\n\t\t}\"\n\t>\n\t\t<view\n\t\t    class=\"u-no-network\"\n\t\t>\n\t\t\t<u-icon\n\t\t\t    :name=\"image\"\n\t\t\t    size=\"150\"\n\t\t\t    imgMode=\"widthFit\"\n\t\t\t    class=\"u-no-network__error-icon\"\n\t\t\t></u-icon>\n\t\t\t<text class=\"u-no-network__tips\">{{tips}}</text>\n\t\t\t<!-- 只有APP平台，才能跳转设置页，因为需要调用plus环境 -->\n\t\t\t<!-- #ifdef APP-PLUS -->\n\t\t\t<view class=\"u-no-network__app\">\n\t\t\t\t<text class=\"u-no-network__app__setting\">请检查网络，或前往</text>\n\t\t\t\t<text\n\t\t\t\t    class=\"u-no-network__app__to-setting\"\n\t\t\t\t    @tap=\"openSettings\"\n\t\t\t\t>设置</text>\n\t\t\t</view>\n\t\t\t<!-- #endif -->\n\t\t\t<view class=\"u-no-network__retry\">\n\t\t\t\t<u-button\n\t\t\t\t    size=\"mini\"\n\t\t\t\t    text=\"重试\"\n\t\t\t\t    type=\"primary\"\n\t\t\t\t\tplain\n\t\t\t\t    @click=\"retry\"\n\t\t\t\t></u-button>\n\t\t\t</view>\n\t\t</view>\n\t</u-overlay>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * noNetwork 无网络提示\n\t * @description 该组件无需任何配置，引入即可，内部自动处理所有功能和事件。\n\t * @tutorial https://www.uviewui.com/components/noNetwork.html\n\t * @property {String}\t\t\ttips \t没有网络时的提示语 （默认：'哎呀，网络信号丢失' ）\n\t * @property {String | Number}\tzIndex\t组件的z-index值 \n\t * @property {String}\t\t\timage\t无网络的图片提示，可用的src地址或base64图片 \n\t * @event {Function}\t\t\tretry\t用户点击页面的\"重试\"按钮时触发\n\t * @example <u-no-network></u-no-network>\n\t */\n\texport default {\n\t\tname: \"u-no-network\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisConnected: true, // 是否有网络连接\n\t\t\t\tnetworkType: \"none\", // 网络类型\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.isIOS = (uni.getSystemInfoSync().platform === 'ios')\n\t\t\tuni.onNetworkStatusChange((res) => {\n\t\t\t\tthis.isConnected = res.isConnected\n\t\t\t\tthis.networkType = res.networkType\n\t\t\t\tthis.emitEvent(this.networkType)\n\t\t\t})\n\t\t\tuni.getNetworkType({\n\t\t\t\tsuccess: (res) => {\n\t\t\t\t\tthis.networkType = res.networkType\n\t\t\t\t\tthis.emitEvent(this.networkType)\n\t\t\t\t\tif (res.networkType == 'none') {\n\t\t\t\t\t\tthis.isConnected = false\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.isConnected = true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\tmethods: {\n\t\t\tretry() {\n\t\t\t\t// 重新检查网络\n\t\t\t\tuni.getNetworkType({\n\t\t\t\t\tsuccess: (res) => {\n\t\t\t\t\t\tthis.networkType = res.networkType\n\t\t\t\t\t\tthis.emitEvent(this.networkType)\n\t\t\t\t\t\tif (res.networkType == 'none') {\n\t\t\t\t\t\t\tuni.$u.toast('无网络连接')\n\t\t\t\t\t\t\tthis.isConnected = false\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tuni.$u.toast('网络已连接')\n\t\t\t\t\t\t\tthis.isConnected = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tthis.$emit('retry')\n\t\t\t},\n\t\t\t// 发出事件给父组件\n\t\t\temitEvent(networkType) {\n\t\t\t\tthis.$emit(networkType === 'none' ? 'disconnected' : 'connected')\n\t\t\t},\n\t\t\tasync openSettings() {\n\t\t\t\tif (this.networkType == \"none\") {\n\t\t\t\t\tthis.openSystemSettings()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t},\n\t\t\topenAppSettings() {\n\t\t\t\tthis.gotoAppSetting()\n\t\t\t},\n\t\t\topenSystemSettings() {\n\t\t\t\t// 以下方法来自5+范畴，如需深究，请自行查阅相关文档\n\t\t\t\t// https://ask.dcloud.net.cn/docs/\n\t\t\t\tif (this.isIOS) {\n\t\t\t\t\tthis.gotoiOSSetting()\n\t\t\t\t} else {\n\t\t\t\t\tthis.gotoAndroidSetting()\n\t\t\t\t}\n\t\t\t},\n\t\t\tnetwork() {\n\t\t\t\tvar result = null\n\t\t\t\tvar cellularData = plus.ios.newObject(\"CTCellularData\")\n\t\t\t\tvar state = cellularData.plusGetAttribute(\"restrictedState\")\n\t\t\t\tif (state == 0) {\n\t\t\t\t\tresult = null\n\t\t\t\t} else if (state == 2) {\n\t\t\t\t\tresult = 1\n\t\t\t\t} else if (state == 1) {\n\t\t\t\t\tresult = 2\n\t\t\t\t}\n\t\t\t\tplus.ios.deleteObject(cellularData)\n\t\t\t\treturn result\n\t\t\t},\n\t\t\tgotoAppSetting() {\n\t\t\t\tif (this.isIOS) {\n\t\t\t\t\tvar UIApplication = plus.ios.import(\"UIApplication\")\n\t\t\t\t\tvar application2 = UIApplication.sharedApplication()\n\t\t\t\t\tvar NSURL2 = plus.ios.import(\"NSURL\")\n\t\t\t\t\tvar setting2 = NSURL2.URLWithString(\"app-settings:\")\n\t\t\t\t\tapplication2.openURL(setting2)\n\t\t\t\t\tplus.ios.deleteObject(setting2)\n\t\t\t\t\tplus.ios.deleteObject(NSURL2)\n\t\t\t\t\tplus.ios.deleteObject(application2)\n\t\t\t\t} else {\n\t\t\t\t\tvar Intent = plus.android.importClass(\"android.content.Intent\")\n\t\t\t\t\tvar Settings = plus.android.importClass(\"android.provider.Settings\")\n\t\t\t\t\tvar Uri = plus.android.importClass(\"android.net.Uri\")\n\t\t\t\t\tvar mainActivity = plus.android.runtimeMainActivity()\n\t\t\t\t\tvar intent = new Intent()\n\t\t\t\t\tintent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)\n\t\t\t\t\tvar uri = Uri.fromParts(\"package\", mainActivity.getPackageName(), null)\n\t\t\t\t\tintent.setData(uri)\n\t\t\t\t\tmainActivity.startActivity(intent)\n\t\t\t\t}\n\t\t\t},\n\t\t\tgotoiOSSetting() {\n\t\t\t\tvar UIApplication = plus.ios.import(\"UIApplication\")\n\t\t\t\tvar application2 = UIApplication.sharedApplication()\n\t\t\t\tvar NSURL2 = plus.ios.import(\"NSURL\")\n\t\t\t\tvar setting2 = NSURL2.URLWithString(\"App-prefs:root=General\")\n\t\t\t\tapplication2.openURL(setting2)\n\t\t\t\tplus.ios.deleteObject(setting2)\n\t\t\t\tplus.ios.deleteObject(NSURL2)\n\t\t\t\tplus.ios.deleteObject(application2)\n\t\t\t},\n\t\t\tgotoAndroidSetting() {\n\t\t\t\tvar Intent = plus.android.importClass(\"android.content.Intent\")\n\t\t\t\tvar Settings = plus.android.importClass(\"android.provider.Settings\")\n\t\t\t\tvar mainActivity = plus.android.runtimeMainActivity()\n\t\t\t\tvar intent = new Intent(Settings.ACTION_SETTINGS)\n\t\t\t\tmainActivity.startActivity(intent)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-no-network {\n\t\t@include flex(column);\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tmargin-top: -100px;\n\n\t\t&__tips {\n\t\t\tcolor: $u-tips-color;\n\t\t\tfont-size: 14px;\n\t\t\tmargin-top: 15px;\n\t\t}\n\n\t\t&__app {\n\t\t\t@include flex(row);\n\t\t\tmargin-top: 6px;\n\n\t\t\t&__setting {\n\t\t\t\tcolor: $u-light-color;\n\t\t\t\tfont-size: 13px;\n\t\t\t}\n\n\t\t\t&__to-setting {\n\t\t\t\tfont-size: 13px;\n\t\t\t\tcolor: $u-primary;\n\t\t\t\tmargin-left: 3px;\n\t\t\t}\n\t\t}\n\n\t\t&__retry {\n\t\t\t@include flex(row);\n\t\t\tjustify-content: center;\n\t\t\tmargin-top: 15px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-notice-bar/props.js",
    "content": "export default {\n    props: {\n        // 显示的内容，数组\n        text: {\n            type: [Array, String],\n            default: uni.$u.props.noticeBar.text\n        },\n        // 通告滚动模式，row-横向滚动，column-竖向滚动\n        direction: {\n            type: String,\n            default: uni.$u.props.noticeBar.direction\n        },\n        // direction = row时，是否使用步进形式滚动\n        step: {\n            type: Boolean,\n            default: uni.$u.props.noticeBar.step\n        },\n        // 是否显示左侧的音量图标\n        icon: {\n            type: String,\n            default: uni.$u.props.noticeBar.icon\n        },\n        // 通告模式，link-显示右箭头，closable-显示右侧关闭图标\n        mode: {\n            type: String,\n            default: uni.$u.props.noticeBar.mode\n        },\n        // 文字颜色，各图标也会使用文字颜色\n        color: {\n            type: String,\n            default: uni.$u.props.noticeBar.color\n        },\n        // 背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.noticeBar.bgColor\n        },\n        // 水平滚动时的滚动速度，即每秒滚动多少px(px)，这有利于控制文字无论多少时，都能有一个恒定的速度\n        speed: {\n            type: [String, Number],\n            default: uni.$u.props.noticeBar.speed\n        },\n        // 字体大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.noticeBar.fontSize\n        },\n        // 滚动一个周期的时间长，单位ms\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.noticeBar.duration\n        },\n        // 是否禁止用手滑动切换\n        // 目前HX2.6.11，只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序\n        disableTouch: {\n            type: Boolean,\n            default: uni.$u.props.noticeBar.disableTouch\n        },\n        // 跳转的页面路径\n        url: {\n            type: String,\n            default: uni.$u.props.noticeBar.url\n        },\n        // 页面跳转的类型\n        linkType: {\n            type: String,\n            default: uni.$u.props.noticeBar.linkType\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-notice-bar/u-notice-bar.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-notice-bar\"\n\t\tv-if=\"show\"\n\t\t:style=\"[{\n\t\t\tbackgroundColor: bgColor\n\t\t}, $u.addStyle(customStyle)]\"\n\t>\n\t\t<template v-if=\"direction === 'column' || (direction === 'row' && step)\">\n\t\t\t<u-column-notice\n\t\t\t\t:color=\"color\"\n\t\t\t\t:bgColor=\"bgColor\"\n\t\t\t\t:text=\"text\"\n\t\t\t\t:mode=\"mode\"\n\t\t\t\t:step=\"step\"\n\t\t\t\t:icon=\"icon\"\n\t\t\t\t:disable-touch=\"disableTouch\"\n\t\t\t\t:fontSize=\"fontSize\"\n\t\t\t\t:duration=\"duration\"\n\t\t\t\t@close=\"close\"\n\t\t\t\t@click=\"click\"\n\t\t\t></u-column-notice>\n\t\t</template>\n\t\t<template v-else>\n\t\t\t<u-row-notice\n\t\t\t\t:color=\"color\"\n\t\t\t\t:bgColor=\"bgColor\"\n\t\t\t\t:text=\"text\"\n\t\t\t\t:mode=\"mode\"\n\t\t\t\t:fontSize=\"fontSize\"\n\t\t\t\t:speed=\"speed\"\n\t\t\t\t:url=\"url\"\n\t\t\t\t:linkType=\"linkType\"\n\t\t\t\t:icon=\"icon\"\n\t\t\t\t@close=\"close\"\n\t\t\t\t@click=\"click\"\n\t\t\t></u-row-notice>\n\t\t</template>\n\t</view>\n</template>\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * noticeBar 滚动通知\n\t * @description 该组件用于滚动通告场景，有多种模式可供选择\n\t * @tutorial https://www.uviewui.com/components/noticeBar.html\n\t * @property {Array | String}\ttext\t\t\t显示的内容，数组\n\t * @property {String}\t\t\tdirection\t\t通告滚动模式，row-横向滚动，column-竖向滚动 ( 默认 'row' )\n\t * @property {Boolean}\t\t\tstep\t\t\tdirection = row时，是否使用步进形式滚动  ( 默认 false )\n\t * @property {String}\t\t\ticon\t\t\t是否显示左侧的音量图标 ( 默认 'volume' )\n\t * @property {String}\t\t\tmode\t\t\t通告模式，link-显示右箭头，closable-显示右侧关闭图标\n\t * @property {String}\t\t\tcolor\t\t\t文字颜色，各图标也会使用文字颜色 ( 默认 '#f9ae3d' )\n\t * @property {String}\t\t\tbgColor\t\t\t背景颜色 ( 默认 '#fdf6ec' )\n\t * @property {String | Number}\tspeed\t\t\t水平滚动时的滚动速度，即每秒滚动多少px(px)，这有利于控制文字无论多少时，都能有一个恒定的速度 ( 默认 80 )\n\t * @property {String | Number}\tfontSize\t\t字体大小 ( 默认 14 )\n\t * @property {String | Number}\tduration\t\t滚动一个周期的时间长，单位ms ( 默认 2000 )\n\t * @property {Boolean}\t\t\tdisableTouch\t是否禁止用手滑动切换 目前HX2.6.11，只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序（默认34） ( 默认 true )\n\t * @property {String}\t\t\turl\t\t\t\t跳转的页面路径\n\t * @property {String}\t\t\tlinkType\t\t页面跳转的类型 ( 默认 navigateTo )\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\n\t * \n\t * @event {Function}\t\t\tclick\t\t\t点击通告文字触发\n\t * @event {Function}\t\t\tclose\t\t\t点击右侧关闭图标触发\n\t * @example <u-notice-bar :more-icon=\"true\" :list=\"list\"></u-notice-bar>\n\t */\n\texport default {\n\t\tname: \"u-notice-bar\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tshow: true\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击通告栏\n\t\t\tclick(index) {\n\t\t\t\tthis.$emit('click', index)\n\t\t\t\tif (this.url && this.linkType) {\n\t\t\t\t\t// 此方法写在mixin中，另外跳转的url和linkType参数也在mixin的props中\n\t\t\t\t\tthis.openPage()\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 点击关闭按钮\n\t\t\tclose() {\n\t\t\t\tthis.show = false\n\t\t\t\tthis.$emit('close')\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-notice-bar {\n\t\toverflow: hidden;\n\t\tpadding: 9px 12px;\n\t\tflex: 1;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-notify/props.js",
    "content": "export default {\n    props: {\n        // 到顶部的距离\n        top: {\n            type: [String, Number],\n            default: uni.$u.props.notify.top\n        },\n        // 是否展示组件\n        // show: {\n        // \ttype: Boolean,\n        // \tdefault: uni.$u.props.notify.show\n        // },\n        // type主题，primary，success，warning，error\n        type: {\n            type: String,\n            default: uni.$u.props.notify.type\n        },\n        // 字体颜色\n        color: {\n            type: String,\n            default: uni.$u.props.notify.color\n        },\n        // 背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.notify.bgColor\n        },\n        // 展示的文字内容\n        message: {\n            type: String,\n            default: uni.$u.props.notify.message\n        },\n        // 展示时长，为0时不消失，单位ms\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.notify.duration\n        },\n        // 字体大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.notify.fontSize\n        },\n        // 是否留出顶部安全距离（状态栏高度）\n        safeAreaInsetTop: {\n            type: Boolean,\n            default: uni.$u.props.notify.safeAreaInsetTop\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-notify/u-notify.vue",
    "content": "<template>\n\t<u-transition\n\t\tmode=\"slide-down\"\n\t\t:customStyle=\"containerStyle\"\n\t\t:show=\"open\"\n\t>\n\t\t<view\n\t\t\tclass=\"u-notify\"\n\t\t\t:class=\"[`u-notify--${tmpConfig.type}`]\"\n\t\t\t:style=\"[backgroundColor, $u.addStyle(customStyle)]\"\n\t\t>\n\t\t\t<u-status-bar v-if=\"tmpConfig.safeAreaInsetTop\"></u-status-bar>\n\t\t\t<view class=\"u-notify__warpper\">\n\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t<u-icon\n\t\t\t\t\t\tv-if=\"['success', 'warning', 'error'].includes(tmpConfig.type)\"\n\t\t\t\t\t\t:name=\"tmpConfig.icon\"\n\t\t\t\t\t\t:color=\"tmpConfig.color\"\n\t\t\t\t\t\t:size=\"1.3 * tmpConfig.fontSize\"\n\t\t\t\t\t\t:customStyle=\"{marginRight: '4px'}\"\n\t\t\t\t\t></u-icon>\n\t\t\t\t</slot>\n\t\t\t\t<text\n\t\t\t\t\tclass=\"u-notify__warpper__text\"\n\t\t\t\t\t:style=\"{\n\t\t\t\t\t\tfontSize: $u.addUnit(tmpConfig.fontSize),\n\t\t\t\t\t\tcolor: tmpConfig.color\n\t\t\t\t\t}\"\n\t\t\t\t>{{ tmpConfig.message }}</text>\n\t\t\t</view>\n\t\t</view>\n\t</u-transition>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * notify 顶部提示\n\t * @description 该组件一般用于页面顶部向下滑出一个提示，尔后自动收起的场景\n\t * @tutorial\n\t * @property {String | Number}\ttop\t\t\t\t\t到顶部的距离 ( 默认 0 )\n\t * @property {String}\t\t\ttype\t\t\t\t主题，primary，success，warning，error ( 默认 'primary' )\n\t * @property {String}\t\t\tcolor\t\t\t\t字体颜色 ( 默认 '#ffffff' )\n\t * @property {String}\t\t\tbgColor\t\t\t\t背景颜色\n\t * @property {String}\t\t\tmessage\t\t\t\t展示的文字内容\n\t * @property {String | Number}\tduration\t\t\t展示时长，为0时不消失，单位ms ( 默认 3000 )\n\t * @property {String | Number}\tfontSize\t\t\t字体大小 ( 默认 15 )\n\t * @property {Boolean}\t\t\tsafeAreaInsetTop\t是否留出顶部安全距离（状态栏高度） ( 默认 false )\n\t * @property {Object}\t\t\tcustomStyle\t\t\t组件的样式，对象形式\n\t * @event {Function}\topen\t开启组件时调用的函数\n\t * @event {Function}\tclose\t关闭组件式调用的函数\n\t * @example <u-notify message=\"Hi uView\"></u-notify>\n\t */\n\texport default {\n\t\tname: 'u-notify',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 是否展示组件\n\t\t\t\topen: false,\n\t\t\t\ttimer: null,\n\t\t\t\tconfig: {\n\t\t\t\t\t// 到顶部的距离\n\t\t\t\t\ttop: uni.$u.props.notify.top,\n\t\t\t\t\t// type主题，primary，success，warning，error\n\t\t\t\t\ttype: uni.$u.props.notify.type,\n\t\t\t\t\t// 字体颜色\n\t\t\t\t\tcolor: uni.$u.props.notify.color,\n\t\t\t\t\t// 背景颜色\n\t\t\t\t\tbgColor: uni.$u.props.notify.bgColor,\n\t\t\t\t\t// 展示的文字内容\n\t\t\t\t\tmessage: uni.$u.props.notify.message,\n\t\t\t\t\t// 展示时长，为0时不消失，单位ms\n\t\t\t\t\tduration: uni.$u.props.notify.duration,\n\t\t\t\t\t// 字体大小\n\t\t\t\t\tfontSize: uni.$u.props.notify.fontSize,\n\t\t\t\t\t// 是否留出顶部安全距离（状态栏高度）\n\t\t\t\t\tsafeAreaInsetTop: uni.$u.props.notify.safeAreaInsetTop\n\t\t\t\t},\n\t\t\t\t// 合并后的配置，避免多次调用组件后，可能会复用之前使用的配置参数\n\t\t\t\ttmpConfig: {}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tcontainerStyle() {\n\t\t\t\tlet top = 0\n\t\t\t\tif (this.tmpConfig.top === 0) {\n\t\t\t\t\t// #ifdef H5\n\t\t\t\t\t// H5端，导航栏为普通元素，需要将组件移动到导航栏的下边沿\n\t\t\t\t\t// H5的导航栏高度为44px\n\t\t\t\t\ttop = 44\n\t\t\t\t\t// #endif\n\t\t\t\t}\n\t\t\t\tconst style = {\n\t\t\t\t\ttop: uni.$u.addUnit(this.tmpConfig.top === 0 ? top : this.tmpConfig.top),\n\t\t\t\t\t// 因为组件底层为u-transition组件，必须将其设置为fixed定位\n\t\t\t\t\t// 让其出现在导航栏底部\n\t\t\t\t\tposition: 'fixed',\n\t\t\t\t\tleft: 0,\n\t\t\t\t\tright: 0,\n\t\t\t\t\tzIndex: 10076\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 组件背景颜色\n\t\t\tbackgroundColor() {\n\t\t\t\tconst style = {}\n\t\t\t\tif (this.tmpConfig.bgColor) {\n\t\t\t\t\tstyle.backgroundColor = this.tmpConfig.bgColor\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 默认主题下的图标\n\t\t\ticon() {\n\t\t\t\tlet icon\n\t\t\t\tif (this.tmpConfig.type === 'success') {\n\t\t\t\t\ticon = 'checkmark-circle'\n\t\t\t\t} else if (this.tmpConfig.type === 'error') {\n\t\t\t\t\ticon = 'close-circle'\n\t\t\t\t} else if (this.tmpConfig.type === 'warning') {\n\t\t\t\t\ticon = 'error-circle'\n\t\t\t\t}\n\t\t\t\treturn icon\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\t// 通过主题的形式调用toast，批量生成方法函数\n\t\t\t['primary', 'success', 'error', 'warning'].map(item => {\n\t\t\t\tthis[item] = message => this.show({\n\t\t\t\t\ttype: item,\n\t\t\t\t\tmessage\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t\tmethods: {\n\t\t\tshow(options) {\n\t\t\t\t// 不将结果合并到this.config变量，避免多次调用u-toast，前后的配置造成混乱\n\t\t\t\tthis.tmpConfig = uni.$u.deepMerge(this.config, options)\n\t\t\t\t// 任何定时器初始化之前，都要执行清除操作，否则可能会造成混乱\n\t\t\t\tthis.clearTimer()\n\t\t\t\tthis.open = true\n\t\t\t\tif (this.tmpConfig.duration > 0) {\n\t\t\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\t\t\tthis.open = false\n\t\t\t\t\t\t// 倒计时结束，清除定时器，隐藏toast组件\n\t\t\t\t\t\tthis.clearTimer()\n\t\t\t\t\t\t// 判断是否存在callback方法，如果存在就执行\n\t\t\t\t\t\ttypeof(this.tmpConfig.complete) === 'function' && this.tmpConfig.complete()\n\t\t\t\t\t}, this.tmpConfig.duration)\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 关闭notify\n\t\t\tclose() {\n\t\t\t\tthis.clearTimer()\n\t\t\t},\n\t\t\tclearTimer() {\n\t\t\t\tthis.open = false\n\t\t\t\t// 清除定时器\n\t\t\t\tclearTimeout(this.timer)\n\t\t\t\tthis.timer = null\n\t\t\t}\n\t\t},\n\t\tbeforeDestroy() {\n\t\t\tthis.clearTimer()\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t$u-notify-padding: 8px 10px !default;\n\t$u-notify-text-font-size: 15px !default;\n\t$u-notify-primary-bgColor: $u-primary !default;\n\t$u-notify-success-bgColor: $u-success !default;\n\t$u-notify-error-bgColor: $u-error !default;\n\t$u-notify-warning-bgColor: $u-warning !default;\n\n\n\t.u-notify {\n\t\tpadding: $u-notify-padding;\n\n\t\t&__warpper {\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t\ttext-align: center;\n\t\t\tjustify-content: center;\n\n\t\t\t&__text {\n\t\t\t\tfont-size: $u-notify-text-font-size;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t}\n\n\t\t&--primary {\n\t\t\tbackground-color: $u-notify-primary-bgColor;\n\t\t}\n\n\t\t&--success {\n\t\t\tbackground-color: $u-notify-success-bgColor;\n\t\t}\n\n\t\t&--error {\n\t\t\tbackground-color: $u-notify-error-bgColor;\n\t\t}\n\n\t\t&--warning {\n\t\t\tbackground-color: $u-notify-warning-bgColor;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-number-box/props.js",
    "content": "export default {\n    props: {\n        // 步进器标识符，在change回调返回\n        name: {\n            type: [String, Number],\n            default: uni.$u.props.numberBox.name\n        },\n        // 用于双向绑定的值，初始化时设置设为默认min值(最小值)\n        value: {\n            type: [String, Number],\n            default: uni.$u.props.numberBox.value\n        },\n        // 最小值\n        min: {\n            type: [String, Number],\n            default: uni.$u.props.numberBox.min\n        },\n        // 最大值\n        max: {\n            type: [String, Number],\n            default: uni.$u.props.numberBox.max\n        },\n        // 加减的步长，可为小数\n        step: {\n            type: [String, Number],\n            default: uni.$u.props.numberBox.step\n        },\n        // 是否只允许输入整数\n        integer: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.integer\n        },\n        // 是否禁用，包括输入框，加减按钮\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.disabled\n        },\n        // 是否禁用输入框\n        disabledInput: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.disabledInput\n        },\n        // 是否开启异步变更，开启后需要手动控制输入值\n        asyncChange: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.asyncChange\n        },\n        // 输入框宽度，单位为px\n        inputWidth: {\n            type: [String, Number],\n            default: uni.$u.props.numberBox.inputWidth\n        },\n        // 是否显示减少按钮\n        showMinus: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.showMinus\n        },\n        // 是否显示增加按钮\n        showPlus: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.showPlus\n        },\n        // 显示的小数位数\n        decimalLength: {\n            type: [String, Number, null],\n            default: uni.$u.props.numberBox.decimalLength\n        },\n        // 是否开启长按加减手势\n        longPress: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.longPress\n        },\n        // 输入框文字和加减按钮图标的颜色\n        color: {\n            type: String,\n            default: uni.$u.props.numberBox.color\n        },\n        // 按钮大小，宽高等于此值，单位px，输入框高度和此值保持一致\n        buttonSize: {\n            type: [String, Number],\n            default: uni.$u.props.numberBox.buttonSize\n        },\n        // 输入框和按钮的背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.numberBox.bgColor\n        },\n        // 指定光标于键盘的距离，避免键盘遮挡输入框，单位px\n        cursorSpacing: {\n            type: [String, Number],\n            default: uni.$u.props.numberBox.cursorSpacing\n        },\n        // 是否禁用增加按钮\n        disablePlus: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.disablePlus\n        },\n        // 是否禁用减少按钮\n        disableMinus: {\n            type: Boolean,\n            default: uni.$u.props.numberBox.disableMinus\n        },\n        // 加减按钮图标的样式\n        iconStyle: {\n            type: [Object, String],\n            default: uni.$u.props.numberBox.iconStyle\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-number-box/u-number-box.vue",
    "content": "<template>\n\t<view class=\"u-number-box\">\n\t\t<view\n\t\t    class=\"u-number-box__slot\"\n\t\t    @tap.stop=\"clickHandler('minus')\"\n\t\t    @touchstart=\"onTouchStart('minus')\"\n\t\t    @touchend.stop=\"clearTimeout\"\n\t\t    v-if=\"showMinus && $slots.minus\"\n\t\t>\n\t\t\t<slot name=\"minus\" />\n\t\t</view>\n\t\t<view\n\t\t    v-else-if=\"showMinus\"\n\t\t    class=\"u-number-box__minus\"\n\t\t    @tap.stop=\"clickHandler('minus')\"\n\t\t    @touchstart=\"onTouchStart('minus')\"\n\t\t    @touchend.stop=\"clearTimeout\"\n\t\t    hover-class=\"u-number-box__minus--hover\"\n\t\t    hover-stay-time=\"150\"\n\t\t    :class=\"{ 'u-number-box__minus--disabled': isDisabled('minus') }\"\n\t\t    :style=\"[buttonStyle('minus')]\"\n\t\t>\n\t\t\t<u-icon\n\t\t\t    name=\"minus\"\n\t\t\t    :color=\"isDisabled('minus') ? '#c8c9cc' : '#323233'\"\n\t\t\t    size=\"15\"\n\t\t\t    bold\n\t\t\t\t:customStyle=\"iconStyle\"\n\t\t\t></u-icon>\n\t\t</view>\n\n\t\t<slot name=\"input\">\n\t\t\t<input\n\t\t\t    :disabled=\"disabledInput || disabled\"\n\t\t\t    :cursor-spacing=\"getCursorSpacing\"\n\t\t\t    :class=\"{ 'u-number-box__input--disabled': disabled || disabledInput }\"\n\t\t\t    v-model=\"currentValue\"\n\t\t\t    class=\"u-number-box__input\"\n\t\t\t    @blur=\"onBlur\"\n\t\t\t    @focus=\"onFocus\"\n\t\t\t    @input=\"onInput\"\n\t\t\t    type=\"number\"\n\t\t\t    :style=\"[inputStyle]\"\n\t\t\t/>\n\t\t</slot>\n\t\t<view\n\t\t    class=\"u-number-box__slot\"\n\t\t    @tap.stop=\"clickHandler('plus')\"\n\t\t    @touchstart=\"onTouchStart('plus')\"\n\t\t    @touchend.stop=\"clearTimeout\"\n\t\t    v-if=\"showPlus && $slots.plus\"\n\t\t>\n\t\t\t<slot name=\"plus\" />\n\t\t</view>\n\t\t<view\n\t\t    v-else-if=\"showPlus\"\n\t\t    class=\"u-number-box__plus\"\n\t\t    @tap.stop=\"clickHandler('plus')\"\n\t\t    @touchstart=\"onTouchStart('plus')\"\n\t\t    @touchend.stop=\"clearTimeout\"\n\t\t    hover-class=\"u-number-box__plus--hover\"\n\t\t    hover-stay-time=\"150\"\n\t\t    :class=\"{ 'u-number-box__minus--disabled': isDisabled('plus') }\"\n\t\t    :style=\"[buttonStyle('plus')]\"\n\t\t>\n\t\t\t<u-icon\n\t\t\t    name=\"plus\"\n\t\t\t    :color=\"isDisabled('plus') ? '#c8c9cc' : '#323233'\"\n\t\t\t    size=\"15\"\n\t\t\t    bold\n\t\t\t\t:customStyle=\"iconStyle\"\n\t\t\t></u-icon>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * numberBox 步进器\n\t * @description 该组件一般用于商城购物选择物品数量的场景。\n\t * @tutorial https://uviewui.com/components/numberBox.html\n\t * @property {String | Number}\tname\t\t\t步进器标识符，在change回调返回\n\t * @property {String | Number}\tvalue\t\t\t用于双向绑定的值，初始化时设置设为默认min值(最小值)  （默认 0 ）\n\t * @property {String | Number}\tmin\t\t\t\t最小值 （默认 1 ）\n\t * @property {String | Number}\tmax\t\t\t\t最大值 （默认 Number.MAX_SAFE_INTEGER ）\n\t * @property {String | Number}\tstep\t\t\t加减的步长，可为小数 （默认 1 ）\n\t * @property {Boolean}\t\t\tinteger\t\t\t是否只允许输入整数 （默认 false ）\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用，包括输入框，加减按钮 （默认 false ）\n\t * @property {Boolean}\t\t\tdisabledInput\t是否禁用输入框 （默认 false ）\n\t * @property {Boolean}\t\t\tasyncChange\t\t是否开启异步变更，开启后需要手动控制输入值 （默认 false ）\n\t * @property {String | Number}\tinputWidth\t\t输入框宽度，单位为px （默认 35 ）\n\t * @property {Boolean}\t\t\tshowMinus\t\t是否显示减少按钮 （默认 true ）\n\t * @property {Boolean}\t\t\tshowPlus\t\t是否显示增加按钮 （默认 true ）\n\t * @property {String | Number}\tdecimalLength\t显示的小数位数\n\t * @property {Boolean}\t\t\tlongPress\t\t是否开启长按加减手势 （默认 true ）\n\t * @property {String}\t\t\tcolor\t\t\t输入框文字和加减按钮图标的颜色 （默认 '#323233' ）\n\t * @property {String | Number}\tbuttonSize\t\t按钮大小，宽高等于此值，单位px，输入框高度和此值保持一致 （默认 30 ）\n\t * @property {String}\t\t\tbgColor\t\t\t输入框和按钮的背景颜色 （默认 '#EBECEE' ）\n\t * @property {String | Number}\tcursorSpacing\t指定光标于键盘的距离，避免键盘遮挡输入框，单位px （默认 100 ）\n\t * @property {Boolean}\t\t\tdisablePlus\t\t是否禁用增加按钮 （默认 false ）\n\t * @property {Boolean}\t\t\tdisableMinus\t是否禁用减少按钮 （默认 false ）\n\t * @property {Object ｜ String}\ticonStyle\t\t加减按钮图标的样式\n\t *\n\t * @event {Function}\tonFocus\t输入框活动焦点\n\t * @event {Function}\tonBlur\t输入框失去焦点\n\t * @event {Function}\tonInput\t输入框值发生变化\n\t * @event {Function}\tonChange\n\t * @example <u-number-box v-model=\"value\" @change=\"valChange\"></u-number-box>\n\t */\n\texport default {\n\t\tname: 'u-number-box',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 输入框实际操作的值\n\t\t\t\tcurrentValue: '',\n\t\t\t\t// 定时器\n\t\t\t\tlongPressTimer: null\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t// 多个值之间，只要一个值发生变化，都要重新检查check()函数\n\t\t\twatchChange(n) {\n\t\t\t\tthis.check()\n\t\t\t},\n\t\t\t// 监听v-mode的变化，重新初始化内部的值\n\t\t\tvalue(n) {\n\t\t\t\tif (n !== this.currentValue) {\n\t\t\t\t\tthis.currentValue = this.format(this.value)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tgetCursorSpacing() {\n\t\t\t\t// 判断传入的单位，如果为px单位，需要转成px\n\t\t\t\treturn uni.$u.getPx(this.cursorSpacing)\n\t\t\t},\n\t\t\t// 按钮的样式\n\t\t\tbuttonStyle() {\n\t\t\t\treturn (type) => {\n\t\t\t\t\tconst style = {\n\t\t\t\t\t\tbackgroundColor: this.bgColor,\n\t\t\t\t\t\theight: uni.$u.addUnit(this.buttonSize),\n\t\t\t\t\t\tcolor: this.color\n\t\t\t\t\t}\n\t\t\t\t\tif (this.isDisabled(type)) {\n\t\t\t\t\t\tstyle.backgroundColor = '#f7f8fa'\n\t\t\t\t\t}\n\t\t\t\t\treturn style\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 输入框的样式\n\t\t\tinputStyle() {\n\t\t\t\tconst disabled = this.disabled || this.disabledInput\n\t\t\t\tconst style = {\n\t\t\t\t\tcolor: this.color,\n\t\t\t\t\tbackgroundColor: this.bgColor,\n\t\t\t\t\theight: uni.$u.addUnit(this.buttonSize),\n\t\t\t\t\twidth: uni.$u.addUnit(this.inputWidth)\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 用于监听多个值发生变化\n\t\t\twatchChange() {\n\t\t\t\treturn [this.integer, this.decimalLength, this.min, this.max]\n\t\t\t},\n\t\t\tisDisabled() {\n\t\t\t\treturn (type) => {\n\t\t\t\t\tif (type === 'plus') {\n\t\t\t\t\t\t// 在点击增加按钮情况下，判断整体的disabled，是否单独禁用增加按钮，以及当前值是否大于最大的允许值\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\tthis.disabled ||\n\t\t\t\t\t\t\tthis.disablePlus ||\n\t\t\t\t\t\t\tthis.currentValue >= this.max\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\t// 点击减少按钮同理\n\t\t\t\t\treturn (\n\t\t\t\t\t\tthis.disabled ||\n\t\t\t\t\t\tthis.disableMinus ||\n\t\t\t\t\t\tthis.currentValue <= this.min\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tthis.currentValue = this.format(this.value)\n\t\t\t},\n\t\t\t// 格式化整理数据，限制范围\n\t\t\tformat(value) {\n\t\t\t\tvalue = this.filter(value)\n\t\t\t\t// 如果为空字符串，那么设置为0，同时将值转为Number类型\n\t\t\t\tvalue = value === '' ? 0 : +value\n\t\t\t\t// 对比最大最小值，取在min和max之间的值\n\t\t\t\tvalue = Math.max(Math.min(this.max, value), this.min)\n\t\t\t\t// 如果设定了最大的小数位数，使用toFixed去进行格式化\n\t\t\t\tif (this.decimalLength !== null) {\n\t\t\t\t\tvalue = value.toFixed(this.decimalLength)\n\t\t\t\t}\n\t\t\t\treturn value\n\t\t\t},\n\t\t\t// 过滤非法的字符\n\t\t\tfilter(value) {\n\t\t\t\t// 只允许0-9之间的数字，\".\"为小数点，\"-\"为负数时候使用\n\t\t\t\tvalue = String(value).replace(/[^0-9.-]/g, '')\n\t\t\t\t// 如果只允许输入整数，则过滤掉小数点后的部分\n\t\t\t\tif (this.integer && value.indexOf('.') !== -1) {\n\t\t\t\t\tvalue = value.split('.')[0]\n\t\t\t\t}\n\t\t\t\treturn value;\n\t\t\t},\n\t\t\tcheck() {\n\t\t\t\t// 格式化了之后，如果前后的值不相等，那么设置为格式化后的值\n\t\t\t\tconst val = this.format(this.currentValue);\n\t\t\t\tif (val !== this.currentValue) {\n\t\t\t\t\tthis.currentValue = val\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 判断是否出于禁止操作状态\n\t\t\t// isDisabled(type) {\n\t\t\t// \tif (type === 'plus') {\n\t\t\t// \t\t// 在点击增加按钮情况下，判断整体的disabled，是否单独禁用增加按钮，以及当前值是否大于最大的允许值\n\t\t\t// \t\treturn (\n\t\t\t// \t\t\tthis.disabled ||\n\t\t\t// \t\t\tthis.disablePlus ||\n\t\t\t// \t\t\tthis.currentValue >= this.max\n\t\t\t// \t\t)\n\t\t\t// \t}\n\t\t\t// \t// 点击减少按钮同理\n\t\t\t// \treturn (\n\t\t\t// \t\tthis.disabled ||\n\t\t\t// \t\tthis.disableMinus ||\n\t\t\t// \t\tthis.currentValue <= this.min\n\t\t\t// \t)\n\t\t\t// },\n\t\t\t// 输入框活动焦点\n\t\t\tonFocus(event) {\n\t\t\t\tthis.$emit('focus', {\n\t\t\t\t\t...event.detail,\n\t\t\t\t\tname: this.name,\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 输入框失去焦点\n\t\t\tonBlur(event) {\n\t\t\t\t// 对输入值进行格式化\n\t\t\t\tconst value = this.format(event.detail.value)\n\t\t\t\t// 发出blur事件\n\t\t\t\tthis.$emit(\n\t\t\t\t\t'blur',{\n\t\t\t\t\t\t...event.detail,\n\t\t\t\t\t\tname: this.name,\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t},\n\t\t\t// 输入框值发生变化\n\t\t\tonInput(e) {\n\t\t\t\tconst {\n\t\t\t\t\tvalue = ''\n\t\t\t\t} = e.detail || {}\n\t\t\t\t// 为空返回\n\t\t\t\tif (value === '') return\n\t\t\t\tlet formatted = this.filter(value)\n\t\t\t\t// 最大允许的小数长度\n\t\t\t\tif (this.decimalLength !== null && formatted.indexOf('.') !== -1) {\n\t\t\t\t\tconst pair = formatted.split('.');\n\t\t\t\t\tformatted = `${pair[0]}.${pair[1].slice(0, this.decimalLength)}`\n\t\t\t\t}\n\t\t\t\tformatted = this.format(formatted)\n\t\t\t\tthis.emitChange(formatted);\n\t\t\t},\n\t\t\t// 发出change事件\n\t\t\temitChange(value) {\n\t\t\t\t// 如果开启了异步变更值，则不修改内部的值，需要用户手动在外部通过v-model变更\n\t\t\t\tif (!this.asyncChange) {\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.$emit('input', value)\n\t\t\t\t\t\tthis.currentValue = value\n\t\t\t\t\t\tthis.$forceUpdate()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\tthis.$emit('change', {\n\t\t\t\t\tvalue,\n\t\t\t\t\tname: this.name,\n\t\t\t\t});\n\t\t\t},\n\t\t\tonChange() {\n\t\t\t\tconst {\n\t\t\t\t\ttype\n\t\t\t\t} = this\n\t\t\t\tif (this.isDisabled(type)) {\n\t\t\t\t\treturn this.$emit('overlimit', type)\n\t\t\t\t}\n\t\t\t\tconst diff = type === 'minus' ? -this.step : +this.step\n\t\t\t\tconst value = this.format(this.add(+this.currentValue, diff))\n\t\t\t\tthis.emitChange(value)\n\t\t\t\tthis.$emit(type)\n\t\t\t},\n\t\t\t// 对值扩大后进行四舍五入，再除以扩大因子，避免出现浮点数操作的精度问题\n\t\t\tadd(num1, num2) {\n\t\t\t\tconst cardinal = Math.pow(10, 10);\n\t\t\t\treturn Math.round((num1 + num2) * cardinal) / cardinal\n\t\t\t},\n\t\t\t// 点击加减按钮\n\t\t\tclickHandler(type) {\n\t\t\t\tthis.type = type\n\t\t\t\tthis.onChange()\n\t\t\t},\n\t\t\tlongPressStep() {\n\t\t\t\t// 每隔一段时间，重新调用longPressStep方法，实现长按加减\n\t\t\t\tthis.clearTimeout()\n\t\t\t\tthis.longPressTimer = setTimeout(() => {\n\t\t\t\t\tthis.onChange()\n\t\t\t\t\tthis.longPressStep()\n\t\t\t\t}, 250);\n\t\t\t},\n\t\t\tonTouchStart(type) {\n\t\t\t\tif (!this.longPress) return\n\t\t\t\tthis.clearTimeout()\n\t\t\t\tthis.type = type\n\t\t\t\t// 一定时间后，默认达到长按状态\n\t\t\t\tthis.longPressTimer = setTimeout(() => {\n\t\t\t\t\tthis.onChange()\n\t\t\t\t\tthis.longPressStep()\n\t\t\t\t}, 600)\n\t\t\t},\n\t\t\t// 触摸结束，清除定时器，停止长按加减\n\t\t\tonTouchEnd() {\n\t\t\t\tif (!this.longPress) return\n\t\t\t\tthis.clearTimeout()\n\t\t\t},\n\t\t\t// 清除定时器\n\t\t\tclearTimeout() {\n\t\t\t\tclearTimeout(this.longPressTimer)\n\t\t\t\tthis.longPressTimer = null\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import '../../libs/css/components.scss';\n\n\t$u-numberBox-hover-bgColor: #E6E6E6 !default;\n\t$u-numberBox-disabled-color: #c8c9cc !default;\n\t$u-numberBox-disabled-bgColor: #f7f8fa !default;\n\t$u-numberBox-plus-radius: 4px !default;\n\t$u-numberBox-minus-radius: 4px !default;\n\t$u-numberBox-input-text-align: center !default;\n\t$u-numberBox-input-font-size: 15px !default;\n\t$u-numberBox-input-padding: 0 !default;\n\t$u-numberBox-input-margin: 0 2px !default;\n\t$u-numberBox-input-disabled-color: #c8c9cc !default;\n\t$u-numberBox-input-disabled-bgColor: #f2f3f5 !default;\n\n\t.u-number-box {\n\t\t@include flex(row);\n\t\talign-items: center;\n\n\t\t&__slot {\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\ttouch-action: none;\n\t\t\t/* #endif */\n\t\t}\n\n\t\t&__plus,\n\t\t&__minus {\n\t\t\twidth: 35px;\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\ttouch-action: none;\n\t\t\t/* #endif */\n\n\t\t\t&--hover {\n\t\t\t\tbackground-color: $u-numberBox-hover-bgColor !important;\n\t\t\t}\n\n\t\t\t&--disabled {\n\t\t\t\tcolor: $u-numberBox-disabled-color;\n\t\t\t\tbackground-color: $u-numberBox-disabled-bgColor;\n\t\t\t}\n\t\t}\n\n\t\t&__plus {\n\t\t\tborder-top-right-radius: $u-numberBox-plus-radius;\n\t\t\tborder-bottom-right-radius: $u-numberBox-plus-radius;\n\t\t}\n\n\t\t&__minus {\n\t\t\tborder-top-left-radius: $u-numberBox-minus-radius;\n\t\t\tborder-bottom-left-radius: $u-numberBox-minus-radius;\n\t\t}\n\n\t\t&__input {\n\t\t\tposition: relative;\n\t\t\ttext-align: $u-numberBox-input-text-align;\n\t\t\tfont-size: $u-numberBox-input-font-size;\n\t\t\tpadding: $u-numberBox-input-padding;\n\t\t\tmargin: $u-numberBox-input-margin;\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\n\t\t\t&--disabled {\n\t\t\t\tcolor: $u-numberBox-input-disabled-color;\n\t\t\t\tbackground-color: $u-numberBox-input-disabled-bgColor;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-number-keyboard/props.js",
    "content": "export default {\n    props: {\n        // 键盘的类型，number-数字键盘，card-身份证键盘\n        mode: {\n            type: String,\n            default: uni.$u.props.numberKeyboard.value\n        },\n        // 是否显示键盘的\".\"符号\n        dotDisabled: {\n            type: Boolean,\n            default: uni.$u.props.numberKeyboard.dotDisabled\n        },\n        // 是否打乱键盘按键的顺序\n        random: {\n            type: Boolean,\n            default: uni.$u.props.numberKeyboard.random\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-number-keyboard/u-number-keyboard.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-keyboard\"\n\t\t@touchmove.stop.prevent=\"noop\"\n\t>\n\t\t<view\n\t\t\tclass=\"u-keyboard__button-wrapper\"\n\t\t\tv-for=\"(item, index) in numList\"\n\t\t\t:key=\"index\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-keyboard__button-wrapper__button\"\n\t\t\t\t:style=\"[itemStyle(index)]\"\n\t\t\t\t@tap=\"keyboardClick(item)\"\n\t\t\t\thover-class=\"u-hover-class\"\n\t\t\t\t:hover-stay-time=\"200\"\n\t\t\t>\n\t\t\t\t<text class=\"u-keyboard__button-wrapper__button__text\">{{ item }}</text>\n\t\t\t</view>\n\t\t</view>\n\t\t<view\n\t\t\tclass=\"u-keyboard__button-wrapper\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-keyboard__button-wrapper__button u-keyboard__button-wrapper__button--gray\"\n\t\t\t\thover-class=\"u-hover-class\"\n\t\t\t\t:hover-stay-time=\"200\"\n\t\t\t\t@touchstart.stop=\"backspaceClick\"\n\t\t\t\t@touchend=\"clearTimer\"\n\t\t\t>\n\t\t\t\t<u-icon\n\t\t\t\t\tname=\"backspace\"\n\t\t\t\t\tcolor=\"#303133\"\n\t\t\t\t\tsize=\"28\"\n\t\t\t\t></u-icon>\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * keyboard 键盘组件\n\t * @description\n\t * @tutorial\n\t * @property {String}\tmode\t\t键盘的类型，number-数字键盘，card-身份证键盘\n\t * @property {Boolean}\tdotDisabled\t是否显示键盘的\".\"符号\n\t * @property {Boolean}\trandom\t\t是否打乱键盘按键的顺序\n\t * @event {Function} change\t\t点击键盘触发\n\t * @event {Function} backspace\t点击退格键触发\n\t * @example\n\t */\n\texport default {\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tbackspace: 'backspace', // 退格键内容\n\t\t\t\tdot: '.', // 点\n\t\t\t\ttimer: null, // 长按多次删除的事件监听\n\t\t\t\tcardX: 'X' // 身份证的X符号\n\t\t\t};\n\t\t},\n\t\tcomputed: {\n\t\t\t// 键盘需要显示的内容\n\t\t\tnumList() {\n\t\t\t\tlet tmp = [];\n\t\t\t\tif (this.dotDisabled && this.mode == 'number') {\n\t\t\t\t\tif (!this.random) {\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn uni.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);\n\t\t\t\t\t}\n\t\t\t\t} else if (!this.dotDisabled && this.mode == 'number') {\n\t\t\t\t\tif (!this.random) {\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0];\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn uni.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.mode == 'card') {\n\t\t\t\t\tif (!this.random) {\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0];\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn uni.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 按键的样式，在非乱序&&数字键盘&&不显示点按钮时，index为9时，按键占位两个空间\n\t\t\titemStyle() {\n\t\t\t\treturn index => {\n\t\t\t\t\tlet style = {};\n\t\t\t\t\tif (this.mode == 'number' && this.dotDisabled && index == 9) style.width = '464rpx';\n\t\t\t\t\treturn style;\n\t\t\t\t};\n\t\t\t},\n\t\t\t// 是否让按键显示灰色，只在非乱序&&数字键盘&&且允许点按键的时候\n\t\t\tbtnBgGray() {\n\t\t\t\treturn index => {\n\t\t\t\t\tif (!this.random && index == 9 && (this.mode != 'number' || (this.mode == 'number' && !this\n\t\t\t\t\t\t\t.dotDisabled))) return true;\n\t\t\t\t\telse return false;\n\t\t\t\t};\n\t\t\t},\n\t\t},\n\t\tcreated() {\n\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击退格键\n\t\t\tbackspaceClick() {\n\t\t\t\tthis.$emit('backspace');\n\t\t\t\tclearInterval(this.timer); //再次清空定时器，防止重复注册定时器\n\t\t\t\tthis.timer = null;\n\t\t\t\tthis.timer = setInterval(() => {\n\t\t\t\t\tthis.$emit('backspace');\n\t\t\t\t}, 250);\n\t\t\t},\n\t\t\tclearTimer() {\n\t\t\t\tclearInterval(this.timer);\n\t\t\t\tthis.timer = null;\n\t\t\t},\n\t\t\t// 获取键盘显示的内容\n\t\t\tkeyboardClick(val) {\n\t\t\t\t// 允许键盘显示点模式和触发非点按键时，将内容转为数字类型\n\t\t\t\tif (!this.dotDisabled && val != this.dot && val != this.cardX) val = Number(val);\n\t\t\t\tthis.$emit('change', val);\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-number-keyboard-background-color:rgb(224, 228, 230) !default;\n\t$u-number-keyboard-padding:8px 10rpx 8px 10rpx !default;\n\t$u-number-keyboard-button-width:222rpx !default;\n\t$u-number-keyboard-button-margin:4px 6rpx !default;\n\t$u-number-keyboard-button-border-top-left-radius:4px !default;\n\t$u-number-keyboard-button-border-top-right-radius:4px !default;\n\t$u-number-keyboard-button-border-bottom-left-radius:4px !default;\n\t$u-number-keyboard-button-border-bottom-right-radius:4px !default;\n\t$u-number-keyboard-button-height: 90rpx!default;\n\t$u-number-keyboard-button-background-color:#FFFFFF !default;\n\t$u-number-keyboard-button-box-shadow:0 2px 0px #BBBCBE !default;\n\t$u-number-keyboard-text-font-size:20px !default;\n\t$u-number-keyboard-text-font-weight:500 !default;\n\t$u-number-keyboard-text-color:$u-main-color !default;\n\t$u-number-keyboard-gray-background-color:rgb(200, 202, 210) !default;\n\t$u-number-keyboard-u-hover-class-background-color: #BBBCC6 !default;\n\n\t.u-keyboard {\n\t\t@include flex;\n\t\tflex-direction: row;\n\t\tjustify-content: space-around;\n\t\tbackground-color: $u-number-keyboard-background-color;\n\t\tflex-wrap: wrap;\n\t\tpadding: $u-number-keyboard-padding;\n\n\t\t&__button-wrapper {\n\t\t\tbox-shadow: $u-number-keyboard-button-box-shadow;\n\t\t\tmargin: $u-number-keyboard-button-margin;\n\t\t\tborder-top-left-radius: $u-number-keyboard-button-border-top-left-radius;\n\t\t\tborder-top-right-radius: $u-number-keyboard-button-border-top-right-radius;\n\t\t\tborder-bottom-left-radius: $u-number-keyboard-button-border-bottom-left-radius;\n\t\t\tborder-bottom-right-radius: $u-number-keyboard-button-border-bottom-right-radius;\n\n\t\t\t&__button {\n\t\t\t\twidth: $u-number-keyboard-button-width;\n\t\t\t\theight: $u-number-keyboard-button-height;\n\t\t\t\tbackground-color: $u-number-keyboard-button-background-color;\n\t\t\t\t@include flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\tborder-top-left-radius: $u-number-keyboard-button-border-top-left-radius;\n\t\t\t\tborder-top-right-radius: $u-number-keyboard-button-border-top-right-radius;\n\t\t\t\tborder-bottom-left-radius: $u-number-keyboard-button-border-bottom-left-radius;\n\t\t\t\tborder-bottom-right-radius: $u-number-keyboard-button-border-bottom-right-radius;\n\n\t\t\t\t&__text {\n\t\t\t\t\tfont-size: $u-number-keyboard-text-font-size;\n\t\t\t\t\tfont-weight: $u-number-keyboard-text-font-weight;\n\t\t\t\t\tcolor: $u-number-keyboard-text-color;\n\t\t\t\t}\n\n\t\t\t\t&--gray {\n\t\t\t\t\tbackground-color: $u-number-keyboard-gray-background-color;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t.u-hover-class {\n\t\tbackground-color: $u-number-keyboard-u-hover-class-background-color;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-overlay/props.js",
    "content": "export default {\n    props: {\n        // 是否显示遮罩\n        show: {\n            type: Boolean,\n            default: uni.$u.props.overlay.show\n        },\n        // 层级z-index\n        zIndex: {\n            type: [String, Number],\n            default: uni.$u.props.overlay.zIndex\n        },\n        // 遮罩的过渡时间，单位为ms\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.overlay.duration\n        },\n        // 不透明度值，当做rgba的第四个参数\n        opacity: {\n            type: [String, Number],\n            default: uni.$u.props.overlay.opacity\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-overlay/u-overlay.vue",
    "content": "<template>\n\t<u-transition\n\t    :show=\"show\"\n\t    custom-class=\"u-overlay\"\n\t    :duration=\"duration\"\n\t    :custom-style=\"overlayStyle\"\n\t    @click=\"clickHandler\"\n\t>\n\t\t<slot />\n\t</u-transition>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * overlay 遮罩\n\t * @description 创建一个遮罩层，用于强调特定的页面元素，并阻止用户对遮罩下层的内容进行操作，一般用于弹窗场景\n\t * @tutorial https://www.uviewui.com/components/overlay.html\n\t * @property {Boolean}\t\t\tshow\t\t是否显示遮罩（默认 false ）\n\t * @property {String | Number}\tzIndex\t\tzIndex 层级（默认 10070 ）\n\t * @property {String | Number}\tduration\t动画时长，单位毫秒（默认 300 ）\n\t * @property {String | Number}\topacity\t\t不透明度值，当做rgba的第四个参数 （默认 0.5 ）\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * @event {Function} click 点击遮罩发送事件\n\t * @example <u-overlay :show=\"show\" @click=\"show = false\"></u-overlay>\n\t */\n\texport default {\n\t\tname: \"u-overlay\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\toverlayStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tposition: 'fixed',\n\t\t\t\t\ttop: 0,\n\t\t\t\t\tleft: 0,\n\t\t\t\t\tright: 0,\n\t\t\t\t\tzIndex: this.zIndex,\n\t\t\t\t\tbottom: 0,\n\t\t\t\t\t'background-color': `rgba(0, 0, 0, ${this.opacity})`\n\t\t\t\t}\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('click')\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n     $u-overlay-top:0 !default;\n     $u-overlay-left:0 !default;\n     $u-overlay-width:100% !default;\n     $u-overlay-height:100% !default;\n     $u-overlay-background-color:rgba(0, 0, 0, .7) !default;\n\t.u-overlay {\n\t\tposition: fixed;\n\t\ttop:$u-overlay-top;\n\t\tleft:$u-overlay-left;\n\t\twidth: $u-overlay-width;\n\t\theight:$u-overlay-height;\n\t\tbackground-color:$u-overlay-background-color;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-parse/node/node.vue",
    "content": "<template>\n  <view :id=\"attrs.id\" :class=\"'_'+name+' '+attrs.class\" :style=\"attrs.style\">\n    <block v-for=\"(n, i) in childs\" v-bind:key=\"i\">\n      <!-- 图片 -->\n      <!-- 占位图 -->\n      <image v-if=\"n.name=='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)\" class=\"_img\" :style=\"n.attrs.style\" :src=\"ctrl[i]<0?opts[2]:opts[1]\" mode=\"widthFix\" />\n      <!-- 显示图片 -->\n      <!-- #ifdef H5 || APP-PLUS -->\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\"/>\n      <!-- #endif -->\n      <!-- #ifndef H5 || APP-PLUS -->\n      <image v-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'\" :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\" />\n      <!-- #endif -->\n      <!-- 文本 -->\n      <!-- #ifndef MP-BAIDU -->\n      <text v-else-if=\"n.type=='text'\" decode>{{n.text}}</text>\n      <!-- #endif -->\n      <text v-else-if=\"n.name=='br'\">\\n</text>\n      <!-- 链接 -->\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\">\n        <node name=\"span\" :childs=\"n.children\" :opts=\"opts\" style=\"display:inherit\" />\n      </view>\n      <!-- 视频 -->\n      <!-- #ifdef APP-PLUS -->\n      <view v-else-if=\"n.html\" :id=\"n.attrs.id\" :class=\"'_video '+n.attrs.class\" :style=\"n.attrs.style\" v-html=\"n.html\" />\n      <!-- #endif -->\n      <!-- #ifndef APP-PLUS -->\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\" :poster=\"n.attrs.poster\" :src=\"n.src[ctrl[i]||0]\" :data-i=\"i\" @play=\"play\" @error=\"mediaError\" />\n      <!-- #endif -->\n      <!-- #ifdef H5 || APP-PLUS -->\n      <iframe v-else-if=\"n.name=='iframe'\" :style=\"n.attrs.style\" :allowfullscreen=\"n.attrs.allowfullscreen\" :frameborder=\"n.attrs.frameborder\" :src=\"n.attrs.src\" />\n      <embed v-else-if=\"n.name=='embed'\" :style=\"n.attrs.style\" :src=\"n.attrs.src\" />\n      <!-- #endif -->\n      <!-- #ifndef MP-TOUTIAO -->\n      <!-- 音频 -->\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\" />\n      <!-- #endif -->\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\">\n        <node v-if=\"n.name=='li'\" :childs=\"n.children\" :opts=\"opts\" />\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\">\n          <node v-if=\"tbody.name=='td'||tbody.name=='th'\" :childs=\"tbody.children\" :opts=\"opts\" />\n          <block v-else v-for=\"(tr, y) in tbody.children\" v-bind:key=\"y\">\n            <view v-if=\"tr.name=='td'||tr.name=='th'\" :class=\"'_'+tr.name+' '+tr.attrs.class\" :style=\"tr.attrs.style\">\n              <node :childs=\"tr.children\" :opts=\"opts\" />\n            </view>\n            <view v-else :class=\"'_'+tr.name+' '+tr.attrs.class\" :style=\"tr.attrs.style\">\n              <view v-for=\"(td, z) in tr.children\" v-bind:key=\"z\" :class=\"'_'+td.name+' '+td.attrs.class\" :style=\"td.attrs.style\">\n                <node :childs=\"td.children\" :opts=\"opts\" />\n              </view>\n            </view>\n          </block>\n        </view>\n      </view>\n      \n      <!-- 富文本 -->\n      <!-- #ifdef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->\n      <rich-text v-else-if=\"handler.use(n)\" :id=\"n.attrs.id\" :style=\"n.f\" :nodes=\"[n]\" />\n      <!-- #endif -->\n      <!-- #ifndef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->\n      <rich-text v-else-if=\"!n.c\" :id=\"n.attrs.id\" :style=\"n.f+';display:inline'\" :preview=\"false\" :nodes=\"[n]\" />\n      <!-- #endif -->\n      <!-- 继续递归 -->\n      <view v-else-if=\"n.c==2\" :id=\"n.attrs.id\" :class=\"'_'+n.name+' '+n.attrs.class\" :style=\"n.f+';'+n.attrs.style\">\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\" />\n      </view>\n      <node v-else :style=\"n.f\" :name=\"n.name\" :attrs=\"n.attrs\" :childs=\"n.children\" :opts=\"opts\" />\n    </block>\n  </view>\n</template>\n<script module=\"handler\" lang=\"wxs\">\n// 行内标签列表\nvar inlineTags = {\n  abbr: true,\n  b: true,\n  big: true,\n  code: true,\n  del: true,\n  em: true,\n  i: true,\n  ins: true,\n  label: true,\n  q: true,\n  small: true,\n  span: true,\n  strong: true,\n  sub: true,\n  sup: true\n}\n/**\n * @description 是否使用 rich-text 显示剩余内容\n */\nmodule.exports = {\n  use: function (item) {\n  // 微信和 QQ 的 rich-text inline 布局无效\n  if (inlineTags[item.name] || (item.attrs.style || '').indexOf('display:inline') != -1)\n    return false\n  return !item.c\n  }\n}\n</script>\n<script>\n\nimport node from './node'\nexport default {\n  name: 'node',\n  // #ifdef MP-WEIXIN\n  options: {\n    virtualHost: true\n  },\n  // #endif\n  data() {\n    return {\n      ctrl: {}\n    }\n  },\n  props: {\n    name: String,\n    attrs: {\n      type: Object,\n      default() {\n        return {}\n      }\n    },\n    childs: Array,\n    opts: Array\n  },\n  components: {\n\n    node\n  },\n  mounted() {\n    for (this.root = this.$parent; this.root.$options.name != 'mp-html'; this.root = this.root.$parent);\n    // #ifdef H5 || APP-PLUS\n    if (this.opts[0]) {\n      for (var i = this.childs.length; i--;)\n        if (this.childs[i].name == 'img')\n          break\n      if (i != -1) {\n        this.observer = uni.createIntersectionObserver(this).relativeToViewport({\n          top: 500,\n          bottom: 500\n        })\n        this.observer.observe('._img', res => {\n          if (res.intersectionRatio) {\n            this.$set(this.ctrl, 'load', 1)\n            this.observer.disconnect()\n          }\n        })\n      }\n    }\n    // #endif\n  },\n  beforeDestroy() {\n    // #ifdef H5 || APP-PLUS\n    if (this.observer)\n      this.observer.disconnect()\n    // #endif\n  },\n  methods:{\n    // #ifdef MP-WEIXIN\n    toJSON() { },\n    // #endif\n    /**\n     * @description 播放视频事件\n     * @param {Event} e \n     */\n    play(e) {\n      // #ifndef APP-PLUS\n      if (this.root.pauseVideo) {\n        var flag = false, id = e.target.id\n        for (var i = this.root._videos.length; i--;) {\n          if (this.root._videos[i].id == id)\n            flag = true\n          else\n            this.root._videos[i].pause() // 自动暂停其他视频\n        }\n        // 将自己加入列表\n        if (!flag) {\n          var ctx = uni.createVideoContext(id\n            // #ifndef MP-BAIDU\n            , this\n            // #endif\n          )\n          ctx.id = id\n          this.root._videos.push(ctx)\n        }\n      }\n      // #endif\n    },\n\n    /**\n     * @description 图片点击事件\n     * @param {Event} e \n     */\n    imgTap(e) {\n      var node = this.childs[e.currentTarget.dataset.i]\n      if (node.a)\n        return this.linkTap(node.a)\n      if (node.attrs.ignore)\n        return\n      // #ifdef H5 || APP-PLUS\n      node.attrs.src = node.attrs.src || node.attrs['data-src']\n      // #endif\n      this.root.$emit('imgTap', node.attrs)\n      // 自动预览图片\n      if (this.root.previewImg)\n        uni.previewImage({\n          current: parseInt(node.attrs.i),\n          urls: this.root.imgList\n        })\n    },\n\n    /**\n     * @description 图片长按\n     */\n    imgLongTap(e) {\n      // #ifdef APP-PLUS\n      var attrs = this.childs[e.currentTarget.dataset.i].attrs\n      if (!attrs.ignore)\n        uni.showActionSheet({\n          itemList: ['保存图片'],\n          success: () => {\n            uni.downloadFile({\n              url: this.root.imgList[attrs.i],\n              success: res => {\n                uni.saveImageToPhotosAlbum({\n                  filePath: res.tempFilePath,\n                  success() {\n                    uni.showToast({\n                      title: '保存成功'\n                    })\n                  }\n                })\n              }\n            })\n          }\n        })\n      // #endif\n    },\n\n    /**\n     * @description 图片加载完成事件\n     * @param {Event} e \n     */\n    imgLoad(e) {\n      var i = e.currentTarget.dataset.i\n      // #ifndef H5 || APP-PLUS\n      // 设置原宽度\n      if (!this.childs[i].w)\n        this.$set(this.ctrl, i, e.detail.width)\n      else\n        // #endif\n        // 加载完毕，取消加载中占位图\n        if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] == -1)\n          this.$set(this.ctrl, i, 1)\n    },\n\n    /**\n     * @description 链接点击事件\n     * @param {Event} e \n     */\n    linkTap(e) {\n      var attrs = e.currentTarget ? this.childs[e.currentTarget.dataset.i].attrs : e,\n        href = attrs.href\n      this.root.$emit('linkTap', attrs)\n      if (href) {\n        // 跳转锚点\n        if (href[0] == '#')\n          this.root.navigateTo(href.substring(1)).catch(() => { })\n        // 复制外部链接\n        else if (href.includes('://')) {\n          if (this.root.copyLink) {\n            // #ifdef H5\n            window.open(href)\n            // #endif\n            // #ifdef MP\n            uni.setClipboardData({\n              data: href,\n              success: () =>\n                uni.showToast({\n                  title: '链接已复制'\n                })\n            })\n            // #endif\n            // #ifdef APP-PLUS\n            plus.runtime.openWeb(href)\n            // #endif\n          }\n        }\n        // 跳转页面\n        else\n          uni.navigateTo({\n            url: href,\n            fail() {\n              uni.switchTab({\n                url: href,\n                fail() { }\n              })\n            }\n          })\n      }\n    },\n\n    /**\n     * @description 错误事件\n     * @param {Event} e \n     */\n    mediaError(e) {\n      var i = e.currentTarget.dataset.i,\n        node = this.childs[i]\n      // 加载其他源\n      if (node.name == 'video' || node.name == 'audio') {\n        var index = (this.ctrl[i] || 0) + 1\n        if (index > node.src.length)\n          index = 0\n        if (index < node.src.length)\n          return this.$set(this.ctrl, i, index)\n      }\n      // 显示错误占位图\n      else if (node.name == 'img' && this.opts[2])\n        this.$set(this.ctrl, i, -1)\n      if (this.root)\n        this.root.$emit('error', {\n          source: node.name,\n          attrs: node.attrs,\n          errMsg: e.detail.errMsg\n        })\n    }\n  }\n}\n</script>\n<style>\n/* a 标签默认效果 */\n._a {\n  padding: 1.5px 0 1.5px 0;\n  color: #366092;\n  word-break: break-all;\n}\n\n/* a 标签点击态效果 */\n._hover {\n  text-decoration: underline;\n  opacity: 0.7;\n}\n\n/* 图片默认效果 */\n._img {\n  max-width: 100%;\n  -webkit-touch-callout: none;\n}\n\n/* 内部样式 */\n\n._b,\n._strong {\n  font-weight: bold;\n}\n\n._code {\n  font-family: monospace;\n}\n\n._del {\n  text-decoration: line-through;\n}\n\n._em,\n._i {\n  font-style: italic;\n}\n\n._h1 {\n  font-size: 2em;\n}\n\n._h2 {\n  font-size: 1.5em;\n}\n\n._h3 {\n  font-size: 1.17em;\n}\n\n._h5 {\n  font-size: 0.83em;\n}\n\n._h6 {\n  font-size: 0.67em;\n}\n\n._h1,\n._h2,\n._h3,\n._h4,\n._h5,\n._h6 {\n  display: block;\n  font-weight: bold;\n}\n\n._image {\n  height: 1px;\n}\n\n._ins {\n  text-decoration: underline;\n}\n\n._li {\n  display: list-item;\n}\n\n._ol {\n  list-style-type: decimal;\n}\n\n._ol,\n._ul {\n  display: block;\n  padding-left: 40px;\n  margin: 1em 0;\n}\n\n._q::before {\n  content: '\"';\n}\n\n._q::after {\n  content: '\"';\n}\n\n._sub {\n  font-size: smaller;\n  vertical-align: sub;\n}\n\n._sup {\n  font-size: smaller;\n  vertical-align: super;\n}\n\n._thead,\n._tbody,\n._tfoot {\n  display: table-row-group;\n}\n\n._tr {\n  display: table-row;\n}\n\n._td,\n._th {\n  display: table-cell;\n  vertical-align: middle;\n}\n\n._th {\n  font-weight: bold;\n  text-align: center;\n}\n\n._ul {\n  list-style-type: disc;\n}\n\n._ul ._ul {\n  margin: 0;\n  list-style-type: circle;\n}\n\n._ul ._ul ._ul {\n  list-style-type: square;\n}\n\n._abbr,\n._b,\n._code,\n._del,\n._em,\n._i,\n._ins,\n._label,\n._q,\n._span,\n._strong,\n._sub,\n._sup {\n  display: inline;\n}\n\n/* #ifdef APP-PLUS */\n._video {\n  width: 300px;\n  height: 225px;\n}\n/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-parse/parser.js",
    "content": "'use strict'\n\n/**\n * @fileoverview html 解析器\n */\n// 配置\nconst config = {\n    // 信任的标签（保持标签名不变）\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'),\n    // 块级标签（转为 div，其他的非信任标签转为 span）\n    blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),\n    // 要移除的标签\n    ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),\n    // 自闭合的标签\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'),\n    // html 实体\n    entities: {\n        lt: '<',\n        gt: '>',\n        quot: '\"',\n        apos: \"'\",\n        ensp: '\\u2002',\n        emsp: '\\u2003',\n        nbsp: '\\xA0',\n        semi: ';',\n        ndash: '–',\n        mdash: '—',\n        middot: '·',\n        lsquo: '‘',\n        rsquo: '’',\n        ldquo: '“',\n        rdquo: '”',\n        bull: '•',\n        hellip: '…'\n    },\n    // 默认的标签样式\n    tagStyle: {\n    // #ifndef APP-PLUS-NVUE\n        address: 'font-style:italic',\n        big: 'display:inline;font-size:1.2em',\n        caption: 'display:table-caption;text-align:center',\n        center: 'text-align:center',\n        cite: 'font-style:italic',\n        dd: 'margin-left:40px',\n        mark: 'background-color:yellow',\n        pre: 'font-family:monospace;white-space:pre',\n        s: 'text-decoration:line-through',\n        small: 'display:inline;font-size:0.8em',\n        u: 'text-decoration:underline' // #endif\n\n    }\n}\nconst { windowWidth } = uni.getSystemInfoSync()\nconst blankChar = makeMap(' ,\\r,\\n,\\t,\\f')\nlet idIndex = 0 // #ifdef H5 || APP-PLUS\n\nconfig.ignoreTags.iframe = void 0\nconfig.trustTags.iframe = true\nconfig.ignoreTags.embed = void 0\nconfig.trustTags.embed = true // #endif\n// #ifdef APP-PLUS-NVUE\n\nconfig.ignoreTags.source = void 0\nconfig.ignoreTags.style = void 0 // #endif\n\n/**\n * @description 创建 map\n * @param {String} str 逗号分隔\n */\n\nfunction makeMap(str) {\n    const map = Object.create(null)\n    const list = str.split(',')\n\n    for (let i = list.length; i--;) {\n        map[list[i]] = true\n    }\n\n    return map\n}\n/**\n * @description 解码 html 实体\n * @param {String} str 要解码的字符串\n * @param {Boolean} amp 要不要解码 &amp;\n * @returns {String} 解码后的字符串\n */\n\nfunction decodeEntity(str, amp) {\n    let i = str.indexOf('&')\n\n    while (i != -1) {\n        const j = str.indexOf(';', i + 3)\n        let code = void 0\n        if (j == -1) break\n\n        if (str[i + 1] == '#') {\n            // &#123; 形式的实体\n            code = parseInt((str[i + 2] == 'x' ? '0' : '') + str.substring(i + 2, j))\n            if (!isNaN(code)) str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)\n        } else {\n            // &nbsp; 形式的实体\n            code = str.substring(i + 1, j)\n            if (config.entities[code] || code == 'amp' && amp) str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)\n        }\n\n        i = str.indexOf('&', i + 1)\n    }\n\n    return str\n}\n/**\n * @description html 解析器\n * @param {Object} vm 组件实例\n */\n\nfunction parser(vm) {\n    this.options = vm || {}\n    this.tagStyle = Object.assign(config.tagStyle, this.options.tagStyle)\n    this.imgList = vm.imgList || []\n    this.plugins = vm.plugins || []\n    this.attrs = Object.create(null)\n    this.stack = []\n    this.nodes = []\n}\n/**\n * @description 执行解析\n * @param {String} content 要解析的文本\n */\n\nparser.prototype.parse = function (content) {\n    // 插件处理\n    for (let i = this.plugins.length; i--;) {\n        if (this.plugins[i].onUpdate) content = this.plugins[i].onUpdate(content, config) || content\n    }\n\n    new lexer(this).parse(content) // 出栈未闭合的标签\n\n    while (this.stack.length) {\n        this.popNode()\n    }\n\n    return this.nodes\n}\n/**\n * @description 将标签暴露出来（不被 rich-text 包含）\n */\n\nparser.prototype.expose = function () {\n    // #ifndef APP-PLUS-NVUE\n    for (let i = this.stack.length; i--;) {\n        const item = this.stack[i]\n        if (item.name == 'a' || item.c) return\n        item.c = 1\n    } // #endif\n}\n/**\n * @description 处理插件\n * @param {Object} node 要处理的标签\n * @returns {Boolean} 是否要移除此标签\n */\n\nparser.prototype.hook = function (node) {\n    for (let i = this.plugins.length; i--;) {\n        if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) == false) return false\n    }\n\n    return true\n}\n/**\n * @description 将链接拼接上主域名\n * @param {String} url 需要拼接的链接\n * @returns {String} 拼接后的链接\n */\n\nparser.prototype.getUrl = function (url) {\n    const { domain } = this.options\n\n    if (url[0] == '/') {\n    // // 开头的补充协议名\n        if (url[1] == '/') url = `${domain ? domain.split('://')[0] : 'http'}:${url}` // 否则补充整个域名\n        else if (domain) url = domain + url\n    } else if (domain && !url.includes('data:') && !url.includes('://')) url = `${domain}/${url}`\n\n    return url\n}\n/**\n * @description 解析样式表\n * @param {Object} node 标签\n * @returns {Object}\n */\n\nparser.prototype.parseStyle = function (node) {\n    const { attrs } = node\n    const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))\n    const styleObj = {}\n    let tmp = ''\n\n    if (attrs.id) {\n    // 暴露锚点\n        if (this.options.useAnchor) this.expose(); else if (node.name != 'img' && node.name != 'a' && node.name != 'video' && node.name != 'audio') attrs.id = void 0\n    } // 转换 width 和 height 属性\n\n    if (attrs.width) {\n        styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')\n        attrs.width = void 0\n    }\n\n    if (attrs.height) {\n        styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')\n        attrs.height = void 0\n    }\n\n    for (let i = 0, len = list.length; i < len; i++) {\n        const info = list[i].split(':')\n        if (info.length < 2) continue\n        const key = info.shift().trim().toLowerCase()\n        let value = info.join(':').trim() // 兼容性的 css 不压缩\n\n        if (value[0] == '-' && value.lastIndexOf('-') > 0 || value.includes('safe')) tmp += ';'.concat(key, ':').concat(value) // 重复的样式进行覆盖\n        else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {\n        // 填充链接\n            if (value.includes('url')) {\n                let j = value.indexOf('(') + 1\n\n                if (j) {\n                    while (value[j] == '\"' || value[j] == \"'\" || blankChar[value[j]]) {\n                        j++\n                    }\n\n                    value = value.substr(0, j) + this.getUrl(value.substr(j))\n                }\n            } // 转换 rpx（rich-text 内部不支持 rpx）\n            else if (value.includes('rpx')) {\n                value = value.replace(/[0-9.]+\\s*rpx/g, ($) => `${parseFloat($) * windowWidth / 750}px`)\n            }\n\n            styleObj[key] = value\n        }\n    }\n\n    node.attrs.style = tmp\n    return styleObj\n}\n/**\n * @description 解析到标签名\n * @param {String} name 标签名\n * @private\n */\n\nparser.prototype.onTagName = function (name) {\n    this.tagName = this.xml ? name : name.toLowerCase()\n    if (this.tagName == 'svg') this.xml = true // svg 标签内大小写敏感\n}\n/**\n * @description 解析到属性名\n * @param {String} name 属性名\n * @private\n */\n\nparser.prototype.onAttrName = function (name) {\n    name = this.xml ? name : name.toLowerCase()\n\n    if (name.substr(0, 5) == 'data-') {\n    // data-src 自动转为 src\n        if (name == 'data-src' && !this.attrs.src) this.attrName = 'src' // a 和 img 标签保留 data- 的属性，可以在 imgtap 和 linktap 事件中使用\n        else if (this.tagName == 'img' || this.tagName == 'a') this.attrName = name // 剩余的移除以减小大小\n        else this.attrName = void 0\n    } else {\n        this.attrName = name\n        this.attrs[name] = 'T' // boolean 型属性缺省设置\n    }\n}\n/**\n * @description 解析到属性值\n * @param {String} val 属性值\n * @private\n */\n\nparser.prototype.onAttrVal = function (val) {\n    const name = this.attrName || '' // 部分属性进行实体解码\n\n    if (name == 'style' || name == 'href') this.attrs[name] = decodeEntity(val, true) // 拼接主域名\n    else if (name.includes('src')) this.attrs[name] = this.getUrl(decodeEntity(val, true)); else if (name) this.attrs[name] = val\n}\n/**\n * @description 解析到标签开始\n * @param {Boolean} selfClose 是否有自闭合标识 />\n * @private\n */\n\nparser.prototype.onOpenTag = function (selfClose) {\n    // 拼装 node\n    const node = Object.create(null)\n    node.name = this.tagName\n    node.attrs = this.attrs\n    this.attrs = Object.create(null)\n    const { attrs } = node\n    const parent = this.stack[this.stack.length - 1]\n    const siblings = parent ? parent.children : this.nodes\n    const close = this.xml ? selfClose : config.voidTags[node.name] // 转换 embed 标签\n\n    if (node.name == 'embed') {\n    // #ifndef H5 || APP-PLUS\n        const src = attrs.src || '' // 按照后缀名和 type 将 embed 转为 video 或 audio\n\n        if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) node.name = 'video'; else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) node.name = 'audio'\n        if (attrs.autostart) attrs.autoplay = 'T'\n        attrs.controls = 'T' // #endif\n        // #ifdef H5 || APP-PLUS\n\n        this.expose() // #endif\n    } // #ifndef APP-PLUS-NVUE\n    // 处理音视频\n\n    if (node.name == 'video' || node.name == 'audio') {\n    // 设置 id 以便获取 context\n        if (node.name == 'video' && !attrs.id) attrs.id = `v${idIndex++}` // 没有设置 controls 也没有设置 autoplay 的自动设置 controls\n\n        if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T' // 用数组存储所有可用的 source\n\n        node.src = []\n\n        if (attrs.src) {\n            node.src.push(attrs.src)\n            attrs.src = void 0\n        }\n\n        this.expose()\n    } // #endif\n    // 处理自闭合标签\n\n    if (close) {\n        if (!this.hook(node) || config.ignoreTags[node.name]) {\n            // 通过 base 标签设置主域名\n            if (node.name == 'base' && !this.options.domain) this.options.domain = attrs.href // #ifndef APP-PLUS-NVUE\n            // 设置 source 标签（仅父节点为 video 或 audio 时有效）\n            else if (node.name == 'source' && parent && (parent.name == 'video' || parent.name == 'audio') && attrs.src) parent.src.push(attrs.src) // #endif\n\n            return\n        } // 解析 style\n\n        const styleObj = this.parseStyle(node) // 处理图片\n\n        if (node.name == 'img') {\n            if (attrs.src) {\n                // 标记 webp\n                if (attrs.src.includes('webp')) node.webp = 'T' // data url 图片如果没有设置 original-src 默认为不可预览的小图片\n\n                if (attrs.src.includes('data:') && !attrs['original-src']) attrs.ignore = 'T'\n\n                if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {\n                    for (let i = this.stack.length; i--;) {\n                        const item = this.stack[i]\n\n                        if (item.name == 'a') {\n                            node.a = item.attrs\n                            break\n                        } // #ifndef H5 || APP-PLUS\n\n                        const style = item.attrs.style || ''\n\n                        if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || !styleObj.width.includes('%'))) {\n                            styleObj.width = '100% !important'\n                            styleObj.height = ''\n\n                            for (let j = i + 1; j < this.stack.length; j++) {\n                                this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')\n                            }\n                        } else if (style.includes('flex') && styleObj.width == '100%') {\n                            for (let _j = i + 1; _j < this.stack.length; _j++) {\n                                const _style = this.stack[_j].attrs.style || ''\n\n                                if (!_style.includes(';width') && !_style.includes(' width') && _style.indexOf('width') != 0) {\n                                    styleObj.width = ''\n                                    break\n                                }\n                            }\n                        } else if (style.includes('inline-block')) {\n                            if (styleObj.width && styleObj.width[styleObj.width.length - 1] == '%') {\n                                item.attrs.style += `;max-width:${styleObj.width}`\n                                styleObj.width = ''\n                            } else item.attrs.style += ';max-width:100%'\n                        } // #endif\n\n                        item.c = 1\n                    }\n\n                    attrs.i = this.imgList.length.toString()\n\n                    let _src = attrs['original-src'] || attrs.src // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360\n\n                    if (this.imgList.includes(_src)) {\n                        // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位\n                        let _i = _src.indexOf('://')\n\n                        if (_i != -1) {\n                            _i += 3\n\n                            let newSrc = _src.substr(0, _i)\n\n                            for (; _i < _src.length; _i++) {\n                                if (_src[_i] == '/') break\n                                newSrc += Math.random() > 0.5 ? _src[_i].toUpperCase() : _src[_i]\n                            }\n\n                            newSrc += _src.substr(_i)\n                            _src = newSrc\n                        }\n                    } // #endif\n\n                    this.imgList.push(_src) // #ifdef H5 || APP-PLUS\n\n                    if (this.options.lazyLoad) {\n                        attrs['data-src'] = attrs.src\n                        attrs.src = void 0\n                    } // #endif\n                }\n            }\n\n            if (styleObj.display == 'inline') styleObj.display = '' // #ifndef APP-PLUS-NVUE\n\n            if (attrs.ignore) {\n                styleObj['max-width'] = styleObj['max-width'] || '100%'\n                attrs.style += ';-webkit-touch-callout:none'\n            } // #endif\n            // 设置的宽度超出屏幕，为避免变形，高度转为自动\n\n            if (parseInt(styleObj.width) > windowWidth) styleObj.height = void 0 // 记录是否设置了宽高\n\n            if (styleObj.width) {\n                if (styleObj.width.includes('auto')) styleObj.width = ''; else {\n                    node.w = 'T'\n                    if (styleObj.height && !styleObj.height.includes('auto')) node.h = 'T'\n                }\n            }\n        } else if (node.name == 'svg') {\n            siblings.push(node)\n            this.stack.push(node)\n            this.popNode()\n            return\n        }\n\n        for (const key in styleObj) {\n            if (styleObj[key]) attrs.style += ';'.concat(key, ':').concat(styleObj[key].replace(' !important', ''))\n        }\n\n        attrs.style = attrs.style.substr(1) || void 0\n    } else {\n        if (node.name == 'pre' || (attrs.style || '').includes('white-space') && attrs.style.includes('pre')) this.pre = node.pre = true\n        node.children = []\n        this.stack.push(node)\n    } // 加入节点树\n\n    siblings.push(node)\n}\n/**\n * @description 解析到标签结束\n * @param {String} name 标签名\n * @private\n */\n\nparser.prototype.onCloseTag = function (name) {\n    // 依次出栈到匹配为止\n    name = this.xml ? name : name.toLowerCase()\n    let i\n\n    for (i = this.stack.length; i--;) {\n        if (this.stack[i].name == name) break\n    }\n\n    if (i != -1) {\n        while (this.stack.length > i) {\n            this.popNode()\n        }\n    } else if (name == 'p' || name == 'br') {\n        const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes\n        siblings.push({\n            name,\n            attrs: {}\n        })\n    }\n}\n/**\n * @description 处理标签出栈\n * @private\n */\n\nparser.prototype.popNode = function () {\n    const node = this.stack.pop()\n    let { attrs } = node\n    const { children } = node\n    const parent = this.stack[this.stack.length - 1]\n    const siblings = parent ? parent.children : this.nodes\n\n    if (!this.hook(node) || config.ignoreTags[node.name]) {\n    // 获取标题\n        if (node.name == 'title' && children.length && children[0].type == 'text' && this.options.setTitle) {\n            uni.setNavigationBarTitle({\n                title: children[0].text\n            })\n        }\n        siblings.pop()\n        return\n    }\n\n    if (node.pre) {\n    // 是否合并空白符标识\n        node.pre = this.pre = void 0\n\n        for (let i = this.stack.length; i--;) {\n            if (this.stack[i].pre) this.pre = true\n        }\n    }\n\n    const styleObj = {} // 转换 svg\n\n    if (node.name == 'svg') {\n    // #ifndef APP-PLUS-NVUE\n        let src = ''\n        const { style } = attrs\n        attrs.style = ''\n        attrs.xmlns = 'http://www.w3.org/2000/svg';\n\n        (function traversal(node) {\n            src += `<${node.name}`\n\n            for (let item in node.attrs) {\n                const val = node.attrs[item]\n\n                if (val) {\n                    if (item == 'viewbox') item = 'viewBox'\n                    src += ' '.concat(item, '=\"').concat(val, '\"')\n                }\n            }\n\n            if (!node.children) src += '/>'; else {\n                src += '>'\n\n                for (let _i2 = 0; _i2 < node.children.length; _i2++) {\n                    traversal(node.children[_i2])\n                }\n\n                src += `</${node.name}>`\n            }\n        }(node))\n\n        node.name = 'img'\n        node.attrs = {\n            src: `data:image/svg+xml;utf8,${src.replace(/#/g, '%23')}`,\n            style,\n            ignore: 'T'\n        }\n        node.children = void 0 // #endif\n\n        this.xml = false\n        return\n    } // #ifndef APP-PLUS-NVUE\n    // 转换 align 属性\n\n    if (attrs.align) {\n        if (node.name == 'table') {\n            if (attrs.align == 'center') styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'; else styleObj.float = attrs.align\n        } else styleObj['text-align'] = attrs.align\n\n        attrs.align = void 0\n    } // 转换 font 标签的属性\n\n    if (node.name == 'font') {\n        if (attrs.color) {\n            styleObj.color = attrs.color\n            attrs.color = void 0\n        }\n\n        if (attrs.face) {\n            styleObj['font-family'] = attrs.face\n            attrs.face = void 0\n        }\n\n        if (attrs.size) {\n            let size = parseInt(attrs.size)\n\n            if (!isNaN(size)) {\n                if (size < 1) size = 1; else if (size > 7) size = 7\n                styleObj['font-size'] = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'][size - 1]\n            }\n\n            attrs.size = void 0\n        }\n    } // #endif\n    // 一些编辑器的自带 class\n\n    if ((attrs.class || '').includes('align-center')) styleObj['text-align'] = 'center'\n    Object.assign(styleObj, this.parseStyle(node))\n\n    if (parseInt(styleObj.width) > windowWidth) {\n        styleObj['max-width'] = '100%'\n        styleObj['box-sizing'] = 'border-box'\n    } // #ifndef APP-PLUS-NVUE\n\n    if (config.blockTags[node.name]) node.name = 'div' // 未知标签转为 span，避免无法显示\n    else if (!config.trustTags[node.name] && !this.xml) node.name = 'span'\n    if (node.name == 'a' || node.name == 'ad' // #ifdef H5 || APP-PLUS\n  || node.name == 'iframe' // #endif\n    ) this.expose() // #ifdef APP-PLUS\n    else if (node.name == 'video') {\n        let str = '<video style=\"width:100%;height:100%\"' // 空白图占位\n\n        if (!attrs.poster && !attrs.autoplay) attrs.poster = \"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>\"\n\n        for (const item in attrs) {\n            if (attrs[item]) str += ` ${item}=\"${attrs[item]}\"`\n        }\n\n        if (this.options.pauseVideo) str += ' onplay=\"for(var e=document.getElementsByTagName(\\'video\\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()\"'\n        str += '>'\n\n        for (let _i3 = 0; _i3 < node.src.length; _i3++) {\n            str += `<source src=\"${node.src[_i3]}\">`\n        }\n\n        str += '</video>'\n        node.html = str\n    } // #endif\n    // 列表处理\n    else if ((node.name == 'ul' || node.name == 'ol') && node.c) {\n        const types = {\n            a: 'lower-alpha',\n            A: 'upper-alpha',\n            i: 'lower-roman',\n            I: 'upper-roman'\n        }\n\n        if (types[attrs.type]) {\n            attrs.style += `;list-style-type:${types[attrs.type]}`\n            attrs.type = void 0\n        }\n\n        for (let _i4 = children.length; _i4--;) {\n            if (children[_i4].name == 'li') children[_i4].c = 1\n        }\n    } // 表格处理\n    else if (node.name == 'table') {\n        // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现\n        let padding = parseFloat(attrs.cellpadding)\n        let spacing = parseFloat(attrs.cellspacing)\n        const border = parseFloat(attrs.border)\n\n        if (node.c) {\n            // padding 和 spacing 默认 2\n            if (isNaN(padding)) padding = 2\n            if (isNaN(spacing)) spacing = 2\n        }\n\n        if (border) attrs.style += `;border:${border}px solid gray`\n\n        if (node.flag && node.c) {\n            // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现\n            styleObj.display = 'grid'\n\n            if (spacing) {\n                styleObj['grid-gap'] = `${spacing}px`\n                styleObj.padding = `${spacing}px`\n            } // 无间隔的情况下避免边框重叠\n            else if (border) attrs.style += ';border-left:0;border-top:0'\n\n            const width = []\n            // 表格的列宽\n            const trList = []\n            // tr 列表\n            const cells = []\n            // 保存新的单元格\n            const map = {}; // 被合并单元格占用的格子\n\n            (function traversal(nodes) {\n                for (let _i5 = 0; _i5 < nodes.length; _i5++) {\n                    if (nodes[_i5].name == 'tr') trList.push(nodes[_i5]); else traversal(nodes[_i5].children || [])\n                }\n            }(children))\n\n            for (let row = 1; row <= trList.length; row++) {\n                let col = 1\n\n                for (let j = 0; j < trList[row - 1].children.length; j++, col++) {\n                    const td = trList[row - 1].children[j]\n\n                    if (td.name == 'td' || td.name == 'th') {\n                        // 这个格子被上面的单元格占用，则列号++\n                        while (map[`${row}.${col}`]) {\n                            col++\n                        }\n\n                        let _style2 = td.attrs.style || ''\n                        const start = _style2.indexOf('width') ? _style2.indexOf(';width') : 0 // 提取出 td 的宽度\n\n                        if (start != -1) {\n                            let end = _style2.indexOf(';', start + 6)\n\n                            if (end == -1) end = _style2.length\n                            if (!td.attrs.colspan) width[col] = _style2.substring(start ? start + 7 : 6, end)\n                            _style2 = _style2.substr(0, start) + _style2.substr(end)\n                        }\n\n                        _style2 += (border ? ';border:'.concat(border, 'px solid gray') + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? ';padding:'.concat(padding, 'px') : '') // 处理列合并\n\n                        if (td.attrs.colspan) {\n                            _style2 += ';grid-column-start:'.concat(col, ';grid-column-end:').concat(col + parseInt(td.attrs.colspan))\n                            if (!td.attrs.rowspan) _style2 += ';grid-row-start:'.concat(row, ';grid-row-end:').concat(row + 1)\n                            col += parseInt(td.attrs.colspan) - 1\n                        } // 处理行合并\n\n                        if (td.attrs.rowspan) {\n                            _style2 += ';grid-row-start:'.concat(row, ';grid-row-end:').concat(row + parseInt(td.attrs.rowspan))\n                            if (!td.attrs.colspan) _style2 += ';grid-column-start:'.concat(col, ';grid-column-end:').concat(col + 1) // 记录下方单元格被占用\n\n                            for (let k = 1; k < td.attrs.rowspan; k++) {\n                                map[`${row + k}.${col}`] = 1\n                            }\n                        }\n\n                        if (_style2) td.attrs.style = _style2\n                        cells.push(td)\n                    }\n                }\n\n                if (row == 1) {\n                    let temp = ''\n\n                    for (let _i6 = 1; _i6 < col; _i6++) {\n                        temp += `${width[_i6] ? width[_i6] : 'auto'} `\n                    }\n\n                    styleObj['grid-template-columns'] = temp\n                }\n            }\n\n            node.children = cells\n        } else {\n            // 没有使用合并单元格的表格通过 table 布局实现\n            if (node.c) styleObj.display = 'table'\n            if (!isNaN(spacing)) styleObj['border-spacing'] = `${spacing}px`\n\n            if (border || padding) {\n                // 遍历\n                (function traversal(nodes) {\n                    for (let _i7 = 0; _i7 < nodes.length; _i7++) {\n                        const _td = nodes[_i7]\n\n                        if (_td.name == 'th' || _td.name == 'td') {\n                            if (border) _td.attrs.style = 'border:'.concat(border, 'px solid gray;').concat(_td.attrs.style || '')\n                            if (padding) _td.attrs.style = 'padding:'.concat(padding, 'px;').concat(_td.attrs.style || '')\n                        } else if (_td.children) traversal(_td.children)\n                    }\n                }(children))\n            }\n        } // 给表格添加一个单独的横向滚动层\n\n        if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {\n            const table = { ...node }\n            node.name = 'div'\n            node.attrs = {\n                style: 'overflow:auto'\n            }\n            node.children = [table]\n            attrs = table.attrs\n        }\n    } else if ((node.name == 'td' || node.name == 'th') && (attrs.colspan || attrs.rowspan)) {\n        for (let _i8 = this.stack.length; _i8--;) {\n            if (this.stack[_i8].name == 'table') {\n                this.stack[_i8].flag = 1 // 指示含有合并单元格\n\n                break\n            }\n        }\n    } // 转换 ruby\n    else if (node.name == 'ruby') {\n        node.name = 'span'\n\n        for (let _i9 = 0; _i9 < children.length - 1; _i9++) {\n            if (children[_i9].type == 'text' && children[_i9 + 1].name == 'rt') {\n                children[_i9] = {\n                    name: 'div',\n                    attrs: {\n                        style: 'display:inline-block'\n                    },\n                    children: [{\n                        name: 'div',\n                        attrs: {\n                            style: 'font-size:50%;text-align:start'\n                        },\n                        children: children[_i9 + 1].children\n                    }, children[_i9]]\n                }\n                children.splice(_i9 + 1, 1)\n            }\n        }\n    } else if (node.c) {\n        node.c = 2\n\n        for (let _i10 = node.children.length; _i10--;) {\n            if (!node.children[_i10].c || node.children[_i10].name == 'table') node.c = 1\n        }\n    }\n    if ((styleObj.display || '').includes('flex') && !node.c) {\n        for (let _i11 = children.length; _i11--;) {\n            const _item = children[_i11]\n\n            if (_item.f) {\n                _item.attrs.style = (_item.attrs.style || '') + _item.f\n                _item.f = void 0\n            }\n        }\n    } // flex 布局时部分样式需要提取到 rich-text 外层\n\n    const flex = parent && (parent.attrs.style || '').includes('flex') // #ifdef MP-WEIXIN\n  // 检查基础库版本 virtualHost 是否可用\n  && !(node.c && wx.getNFCAdapter) // #endif\n  // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO\n  && !node.c // #endif\n\n    if (flex) node.f = ';max-width:100%' // #endif\n\n    for (const key in styleObj) {\n        if (styleObj[key]) {\n            const val = ';'.concat(key, ':').concat(styleObj[key].replace(' !important', '')) // #ifndef APP-PLUS-NVUE\n\n            if (flex && (key.includes('flex') && key != 'flex-direction' || key == 'align-self' || styleObj[key][0] == '-' || key == 'width' && val.includes('%'))) {\n                node.f += val\n                if (key == 'width') attrs.style += ';width:100%'\n            } else // #endif\n            { attrs.style += val }\n        }\n    }\n\n    attrs.style = attrs.style.substr(1) || void 0\n}\n/**\n * @description 解析到文本\n * @param {String} text 文本内容\n */\n\nparser.prototype.onText = function (text) {\n    if (!this.pre) {\n    // 合并空白符\n        let trim = ''\n        let flag\n\n        for (let i = 0, len = text.length; i < len; i++) {\n            if (!blankChar[text[i]]) trim += text[i]; else {\n                if (trim[trim.length - 1] != ' ') trim += ' '\n                if (text[i] == '\\n' && !flag) flag = true\n            }\n        } // 去除含有换行符的空串\n\n        if (trim == ' ' && flag) return\n        text = trim\n    }\n\n    const node = Object.create(null)\n    node.type = 'text'\n    node.text = decodeEntity(text)\n\n    if (this.hook(node)) {\n        const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes\n        siblings.push(node)\n    }\n}\n/**\n * @description html 词法分析器\n * @param {Object} handler 高层处理器\n */\n\nfunction lexer(handler) {\n    this.handler = handler\n}\n/**\n * @description 执行解析\n * @param {String} content 要解析的文本\n */\n\nlexer.prototype.parse = function (content) {\n    this.content = content || ''\n    this.i = 0 // 标记解析位置\n\n    this.start = 0 // 标记一个单词的开始位置\n\n    this.state = this.text // 当前状态\n\n    for (let len = this.content.length; this.i != -1 && this.i < len;) {\n        this.state()\n    }\n}\n/**\n * @description 检查标签是否闭合\n * @param {String} method 如果闭合要进行的操作\n * @returns {Boolean} 是否闭合\n * @private\n */\n\nlexer.prototype.checkClose = function (method) {\n    const selfClose = this.content[this.i] == '/'\n\n    if (this.content[this.i] == '>' || selfClose && this.content[this.i + 1] == '>') {\n        if (method) this.handler[method](this.content.substring(this.start, this.i))\n        this.i += selfClose ? 2 : 1\n        this.start = this.i\n        this.handler.onOpenTag(selfClose)\n\n        if (this.handler.tagName == 'script') {\n            this.i = this.content.indexOf('</', this.i)\n\n            if (this.i != -1) {\n                this.i += 2\n                this.start = this.i\n            }\n\n            this.state = this.endTag\n        } else this.state = this.text\n\n        return true\n    }\n\n    return false\n}\n/**\n * @description 文本状态\n * @private\n */\n\nlexer.prototype.text = function () {\n    this.i = this.content.indexOf('<', this.i) // 查找最近的标签\n\n    if (this.i == -1) {\n    // 没有标签了\n        if (this.start < this.content.length) this.handler.onText(this.content.substring(this.start, this.content.length))\n        return\n    }\n\n    const c = this.content[this.i + 1]\n\n    if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {\n    // 标签开头\n        if (this.start != this.i) this.handler.onText(this.content.substring(this.start, this.i))\n        this.start = ++this.i\n        this.state = this.tagName\n    } else if (c == '/' || c == '!' || c == '?') {\n        if (this.start != this.i) this.handler.onText(this.content.substring(this.start, this.i))\n        const next = this.content[this.i + 2]\n\n        if (c == '/' && (next >= 'a' && next <= 'z' || next >= 'A' && next <= 'Z')) {\n            // 标签结尾\n            this.i += 2\n            this.start = this.i\n            return this.state = this.endTag\n        } // 处理注释\n\n        let end = '-->'\n        if (c != '!' || this.content[this.i + 2] != '-' || this.content[this.i + 3] != '-') end = '>'\n        this.i = this.content.indexOf(end, this.i)\n\n        if (this.i != -1) {\n            this.i += end.length\n            this.start = this.i\n        }\n    } else this.i++\n}\n/**\n * @description 标签名状态\n * @private\n */\n\nlexer.prototype.tagName = function () {\n    if (blankChar[this.content[this.i]]) {\n    // 解析到标签名\n        this.handler.onTagName(this.content.substring(this.start, this.i))\n\n        while (blankChar[this.content[++this.i]]) {\n\n        }\n\n        if (this.i < this.content.length && !this.checkClose()) {\n            this.start = this.i\n            this.state = this.attrName\n        }\n    } else if (!this.checkClose('onTagName')) this.i++\n}\n/**\n * @description 属性名状态\n * @private\n */\n\nlexer.prototype.attrName = function () {\n    let c = this.content[this.i]\n\n    if (blankChar[c] || c == '=') {\n    // 解析到属性名\n        this.handler.onAttrName(this.content.substring(this.start, this.i))\n        let needVal = c == '='\n        const len = this.content.length\n\n        while (++this.i < len) {\n            c = this.content[this.i]\n\n            if (!blankChar[c]) {\n                if (this.checkClose()) return\n\n                if (needVal) {\n                    // 等号后遇到第一个非空字符\n                    this.start = this.i\n                    return this.state = this.attrVal\n                }\n\n                if (this.content[this.i] == '=') needVal = true; else {\n                    this.start = this.i\n                    return this.state = this.attrName\n                }\n            }\n        }\n    } else if (!this.checkClose('onAttrName')) this.i++\n}\n/**\n * @description 属性值状态\n * @private\n */\n\nlexer.prototype.attrVal = function () {\n    const c = this.content[this.i]\n    const len = this.content.length // 有冒号的属性\n\n    if (c == '\"' || c == \"'\") {\n        this.start = ++this.i\n        this.i = this.content.indexOf(c, this.i)\n        if (this.i == -1) return\n        this.handler.onAttrVal(this.content.substring(this.start, this.i))\n    } // 没有冒号的属性\n    else {\n        for (; this.i < len; this.i++) {\n            if (blankChar[this.content[this.i]]) {\n                this.handler.onAttrVal(this.content.substring(this.start, this.i))\n                break\n            } else if (this.checkClose('onAttrVal')) return\n        }\n    }\n\n    while (blankChar[this.content[++this.i]]) {\n\n    }\n\n    if (this.i < len && !this.checkClose()) {\n        this.start = this.i\n        this.state = this.attrName\n    }\n}\n/**\n * @description 结束标签状态\n * @returns {String} 结束的标签名\n * @private\n */\n\nlexer.prototype.endTag = function () {\n    const c = this.content[this.i]\n\n    if (blankChar[c] || c == '>' || c == '/') {\n        this.handler.onCloseTag(this.content.substring(this.start, this.i))\n\n        if (c != '>') {\n            this.i = this.content.indexOf('>', this.i)\n            if (this.i == -1) return\n        }\n\n        this.start = ++this.i\n        this.state = this.text\n    } else this.i++\n}\n\nmodule.exports = parser\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-parse/props.js",
    "content": "export default {\n    props: {\n        // #ifdef APP-PLUS-NVUE\n        bgColor: String,\n        // #endif\n        content: String,\n        copyLink: {\n\t\t  type: Boolean,\n\t\t  default: uni.$u.props.parse.copyLink\n        },\n        domain: String,\n        errorImg: {\n\t\t  type: String,\n\t\t  default: uni.$u.props.parse.errorImg\n        },\n        lazyLoad: {\n\t\t  type: Boolean,\n\t\t  default: uni.$u.props.parse.lazyLoad\n        },\n        loadingImg: {\n\t\t  type: String,\n\t\t  default: uni.$u.props.parse.loadingImg\n        },\n        pauseVideo: {\n\t\t  type: Boolean,\n\t\t  default: uni.$u.props.parse.pauseVideo\n        },\n        previewImg: {\n\t\t  type: Boolean,\n\t\t  default: uni.$u.props.parse.previewImg\n        },\n        scrollTable: Boolean,\n        selectable: Boolean,\n        setTitle: {\n\t\t  type: Boolean,\n\t\t  default: uni.$u.props.parse.setTitle\n        },\n        showImgMenu: {\n\t\t  type: Boolean,\n\t\t  default: uni.$u.props.parse.showImgMenu\n        },\n        tagStyle: Object,\n        useAnchor: null\n\t  }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-parse/u-parse.vue",
    "content": "<template>\n  <view id=\"_root\" :class=\"(selectable?'_select ':'')+'_root'\">\n    <slot v-if=\"!nodes[0]\" />\n    <!-- #ifndef APP-PLUS-NVUE -->\n    <node v-else :childs=\"nodes\" :opts=\"[lazyLoad,loadingImg,errorImg,showImgMenu]\" />\n    <!-- #endif -->\n    <!-- #ifdef APP-PLUS-NVUE -->\n    <web-view ref=\"web\" src=\"/static/app-plus/mp-html/local.html\" :style=\"'margin-top:-2px;height:' + height + 'px'\" @onPostMessage=\"_onMessage\" />\n    <!-- #endif -->\n  </view>\n</template>\n\n<script>\n\timport props from './props.js';\n/**\n * mp-html v2.0.4\n * @description 富文本组件\n * @tutorial https://github.com/jin-yufeng/mp-html\n * @property {String}\t\t\tbgColor\t\t背景颜色，只适用与APP-PLUS-NVUE\n * @property {String}\t\t\tcontent\t\t用于渲染的富文本字符串（默认 true ）\n * @property {Boolean}\t\t\tcopyLink\t是否允许外部链接被点击时自动复制\n * @property {String}\t\t\tdomain\t\t主域名，用于拼接链接\n * @property {String}\t\t\terrorImg\t图片出错时的占位图链接\n * @property {Boolean}\t\t\tlazyLoad\t是否开启图片懒加载（默认 true ）\n * @property {string}\t\t\tloadingImg\t图片加载过程中的占位图链接\n * @property {Boolean}\t\t\tpauseVideo\t是否在播放一个视频时自动暂停其它视频（默认 true ）\n * @property {Boolean}\t\t\tpreviewImg\t是否允许图片被点击时自动预览（默认 true ）\n * @property {Boolean}\t\t\tscrollTable\t是否给每个表格添加一个滚动层使其能单独横向滚动\n * @property {Boolean}\t\t\tselectable\t是否开启长按复制\n * @property {Boolean}\t\t\tsetTitle\t是否将 title 标签的内容设置到页面标题（默认 true ）\n * @property {Boolean}\t\t\tshowImgMenu\t是否允许图片被长按时显示菜单（默认 true ）\n * @property {Object}\t\t\ttagStyle\t标签的默认样式\n * @property {Boolean | Number}\tuseAnchor\t是否使用锚点链接\n * \n * @event {Function}\tload\tdom 结构加载完毕时触发\n * @event {Function}\tready\t所有图片加载完毕时触发\n * @event {Function}\timgTap\t图片被点击时触发\n * @event {Function}\tlinkTap\t链接被点击时触发\n * @event {Function}\terror\t媒体加载出错时触发\n */\nconst plugins=[]\nconst parser = require('./parser')\n// #ifndef APP-PLUS-NVUE\nimport node from './node/node'\n// #endif\n// #ifdef APP-PLUS-NVUE\nconst dom = weex.requireModule('dom')\n// #endif\nexport default {\n  name: 'mp-html',\n  data() {\n    return {\n      nodes: [],\n      // #ifdef APP-PLUS-NVUE\n      height: 0\n      // #endif\n    }\n  },\n  mixins:[props],\n  // #ifndef APP-PLUS-NVUE\n  components: {\n    node\n  },\n  // #endif\n  watch: {\n    content(content) {\n      this.setContent(content)\n    }\n  },\n  created() {\n    this.plugins = []\n    for (let i = plugins.length; i--;)\n      this.plugins.push(new plugins[i](this))\n  },\n  mounted() {\n    if (this.content && !this.nodes.length)\n      this.setContent(this.content)\n  },\n  beforeDestroy() {\n    this._hook('onDetached')\n    clearInterval(this._timer)\n  },\n  methods: {\n    /**\n     * @description 将锚点跳转的范围限定在一个 scroll-view 内\n     * @param {Object} page scroll-view 所在页面的示例\n     * @param {String} selector scroll-view 的选择器\n     * @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名\n     */\n    in(page, selector, scrollTop) {\n      // #ifndef APP-PLUS-NVUE\n      if (page && selector && scrollTop)\n        this._in = {\n          page,\n          selector,\n          scrollTop\n        }\n      // #endif\n    },\n\n    /**\n     * @description 锚点跳转\n     * @param {String} id 要跳转的锚点 id\n     * @param {Number} offset 跳转位置的偏移量\n     * @returns {Promise}\n     */\n    navigateTo(id, offset) {\n      return new Promise((resolve, reject) => {\n        if (!this.useAnchor)\n          return reject('Anchor is disabled')\n        offset = offset || parseInt(this.useAnchor) || 0\n        // #ifdef APP-PLUS-NVUE\n        if (!id) {\n          dom.scrollToElement(this.$refs.web, {\n            offset\n          })\n          resolve()\n        } else {\n          this._navigateTo = {\n            resolve,\n            reject,\n            offset\n          }\n          this.$refs.web.evalJs('uni.postMessage({data:{action:\"getOffset\",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')\n        }\n        // #endif\n        // #ifndef APP-PLUS-NVUE\n        let deep = ' '\n        // #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO\n        deep = '>>>'\n        // #endif\n        const selector = uni.createSelectorQuery()\n          // #ifndef MP-ALIPAY\n          .in(this._in ? this._in.page : this)\n          // #endif\n          .select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()\n        if (this._in)\n          selector.select(this._in.selector).scrollOffset()\n            .select(this._in.selector).boundingClientRect() // 获取 scroll-view 的位置和滚动距离\n        else\n          selector.selectViewport().scrollOffset() // 获取窗口的滚动距离\n        selector.exec(res => {\n          if (!res[0])\n            return reject('Label not found')\n          const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset\n          if (this._in)\n            // scroll-view 跳转\n            this._in.page[this._in.scrollTop] = scrollTop\n          else\n            // 页面跳转\n            uni.pageScrollTo({\n              scrollTop,\n              duration: 300\n            })\n          resolve()\n        })\n        // #endif\n      })\n    },\n\n    /**\n     * @description 获取文本内容\n     * @return {String}\n     */\n    getText() {\n      let text = '';\n      (function traversal(nodes) {\n        for (let i = 0; i < nodes.length; i++) {\n          const node = nodes[i]\n          if (node.type == 'text')\n            text += node.text.replace(/&amp;/g, '&')\n          else if (node.name == 'br')\n            text += '\\n'\n          else {\n            // 块级标签前后加换行\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')\n            if (isBlock && text && text[text.length - 1] != '\\n')\n              text += '\\n'\n            // 递归获取子节点的文本\n            if (node.children)\n              traversal(node.children)\n            if (isBlock && text[text.length - 1] != '\\n')\n              text += '\\n'\n            else if (node.name == 'td' || node.name == 'th')\n              text += '\\t'\n          }\n        }\n      })(this.nodes)\n      return text\n    },\n\n    /**\n     * @description 获取内容大小和位置\n     * @return {Promise}\n     */\n    getRect() {\n      return new Promise((resolve, reject) => {\n        uni.createSelectorQuery()\n          // #ifndef MP-ALIPAY\n          .in(this)\n          // #endif\n          .select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject('Root label not found'))\n      })\n    },\n\n    /**\n     * @description 设置内容\n     * @param {String} content html 内容\n     * @param {Boolean} append 是否在尾部追加\n     */\n    setContent(content, append) {\n      if (!append || !this.imgList)\n        this.imgList = []\n      const nodes = new parser(this).parse(content)\n      // #ifdef APP-PLUS-NVUE\n      if (this._ready)\n        this._set(nodes, append)\n      // #endif\n      this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)\n\n      // #ifndef APP-PLUS-NVUE\n      this._videos = []\n      this.$nextTick(() => {\n        this._hook('onLoad')\n        this.$emit('load')\n      })\n\n      // 等待图片加载完毕\n      let height\n      clearInterval(this._timer)\n      this._timer = setInterval(() => {\n        this.getRect().then(rect => {\n          // 350ms 总高度无变化就触发 ready 事件\n          if (rect.height == height) {\n            this.$emit('ready', rect)\n            clearInterval(this._timer)\n          }\n          height = rect.height\n        }).catch(() => { })\n      }, 350)\n      // #endif\n    },\n\n    /**\n     * @description 调用插件钩子函数\n     */\n    _hook(name) {\n      for (let i = plugins.length; i--;)\n        if (this.plugins[i][name])\n          this.plugins[i][name]()\n    },\n\n    // #ifdef APP-PLUS-NVUE\n    /**\n     * @description 设置内容\n     */\n    _set(nodes, append) {\n      this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes) + ',' + JSON.stringify([this.bgColor, this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')\n    },\n\n    /**\n     * @description 接收到 web-view 消息\n     */\n    _onMessage(e) {\n      const message = e.detail.data[0]\n      switch (message.action) {\n        // web-view 初始化完毕\n        case 'onJSBridgeReady':\n          this._ready = true\n          if (this.nodes)\n            this._set(this.nodes)\n          break\n        // 内容 dom 加载完毕\n        case 'onLoad':\n          this.height = message.height\n          this._hook('onLoad')\n          this.$emit('load')\n          break\n        // 所有图片加载完毕\n        case 'onReady':\n          this.getRect().then(res => {\n            this.$emit('ready', res)\n          }).catch(() => { })\n          break\n        // 总高度发生变化\n        case 'onHeightChange':\n          this.height = message.height\n          break\n        // 图片点击\n        case 'onImgTap':\n          this.$emit('imgTap', message.attrs)\n          if (this.previewImg)\n            uni.previewImage({\n              current: parseInt(message.attrs.i),\n              urls: this.imgList\n            })\n          break\n        // 链接点击\n        case 'onLinkTap':\n          const href = message.attrs.href\n          this.$emit('linkTap', message.attrs)\n          if (href) {\n            // 锚点跳转\n            if (href[0] == '#') {\n              if (this.useAnchor)\n                dom.scrollToElement(this.$refs.web, {\n                  offset: message.offset\n                })\n            }\n            // 打开外链\n            else if (href.includes('://')) {\n              if (this.copyLink)\n                plus.runtime.openWeb(href)\n            }\n            else\n              uni.navigateTo({\n                url: href,\n                fail() {\n                  wx.switchTab({\n                    url: href\n                  })\n                }\n              })\n          }\n          break\n        // 获取到锚点的偏移量\n        case 'getOffset':\n          if (typeof message.offset == 'number') {\n            dom.scrollToElement(this.$refs.web, {\n              offset: message.offset + this._navigateTo.offset\n            })\n            this._navigateTo.resolve()\n          } else\n            this._navigateTo.reject('Label not found')\n          break\n        // 点击\n        case 'onClick':\n          this.$emit('tap')\n          break\n        // 出错\n        case 'onError':\n          this.$emit('error', {\n            source: message.source,\n            attrs: message.attrs\n          })\n      }\n    }\n    // #endif\n  }\n}\n</script>\n\n<style>\n/* #ifndef APP-PLUS-NVUE */\n/* 根节点样式 */\n._root {\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n}\n\n/* 长按复制 */\n._select {\n  user-select: text;\n}\n/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-picker/props.js",
    "content": "export default {\n    props: {\n        // 是否展示picker弹窗\n        show: {\n            type: Boolean,\n            default: uni.$u.props.picker.show\n        },\n        // 是否展示顶部的操作栏\n        showToolbar: {\n            type: Boolean,\n            default: uni.$u.props.picker.showToolbar\n        },\n        // 顶部标题\n        title: {\n            type: String,\n            default: uni.$u.props.picker.title\n        },\n        // 对象数组，设置每一列的数据\n        columns: {\n            type: Array,\n            default: uni.$u.props.picker.columns\n        },\n        // 是否显示加载中状态\n        loading: {\n            type: Boolean,\n            default: uni.$u.props.picker.loading\n        },\n        // 各列中，单个选项的高度\n        itemHeight: {\n            type: [String, Number],\n            default: uni.$u.props.picker.itemHeight\n        },\n        // 取消按钮的文字\n        cancelText: {\n            type: String,\n            default: uni.$u.props.picker.cancelText\n        },\n        // 确认按钮的文字\n        confirmText: {\n            type: String,\n            default: uni.$u.props.picker.confirmText\n        },\n        // 取消按钮的颜色\n        cancelColor: {\n            type: String,\n            default: uni.$u.props.picker.cancelColor\n        },\n        // 确认按钮的颜色\n        confirmColor: {\n            type: String,\n            default: uni.$u.props.picker.confirmColor\n        },\n        // 每列中可见选项的数量\n        visibleItemCount: {\n            type: [String, Number],\n            default: uni.$u.props.picker.visibleItemCount\n        },\n        // 选项对象中，需要展示的属性键名\n        keyName: {\n            type: String,\n            default: uni.$u.props.picker.keyName\n        },\n        // 是否允许点击遮罩关闭选择器\n        closeOnClickOverlay: {\n            type: Boolean,\n            default: uni.$u.props.picker.closeOnClickOverlay\n        },\n        // 各列的默认索引\n        defaultIndex: {\n            type: Array,\n            default: uni.$u.props.picker.defaultIndex\n        },\n\t\t// 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件，只在微信2.21.1及以上有效\n\t\timmediateChange: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.picker.immediateChange\n\t\t}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-picker/u-picker.vue",
    "content": "<template>\n\t<u-popup\n\t\t:show=\"show\"\n\t\t@close=\"closeHandler\"\n\t>\n\t\t<view class=\"u-picker\">\n\t\t\t<u-toolbar\n\t\t\t\tv-if=\"showToolbar\"\n\t\t\t\t:cancelColor=\"cancelColor\"\n\t\t\t\t:confirmColor=\"confirmColor\"\n\t\t\t\t:cancelText=\"cancelText\"\n\t\t\t\t:confirmText=\"confirmText\"\n\t\t\t\t:title=\"title\"\n\t\t\t\t@cancel=\"cancel\"\n\t\t\t\t@confirm=\"confirm\"\n\t\t\t></u-toolbar>\n\t\t\t<picker-view\n\t\t\t\tclass=\"u-picker__view\"\n\t\t\t\t:indicatorStyle=\"`height: ${$u.addUnit(itemHeight)}`\"\n\t\t\t\t:value=\"innerIndex\"\n\t\t\t\t:immediateChange=\"immediateChange\"\n\t\t\t\t:style=\"{\n\t\t\t\t\theight: `${$u.addUnit(visibleItemCount * itemHeight)}`\n\t\t\t\t}\"\n\t\t\t\t@change=\"changeHandler\"\n\t\t\t>\n\t\t\t\t<picker-view-column\n\t\t\t\t\tv-for=\"(item, index) in innerColumns\"\n\t\t\t\t\t:key=\"index\"\n\t\t\t\t\tclass=\"u-picker__view__column\"\n\t\t\t\t>\n\t\t\t\t\t<text\n\t\t\t\t\t\tv-if=\"$u.test.array(item)\"\n\t\t\t\t\t\tclass=\"u-picker__view__column__item u-line-1\"\n\t\t\t\t\t\tv-for=\"(item1, index1) in item\"\n\t\t\t\t\t\t:key=\"index1\"\n\t\t\t\t\t\t:style=\"{\n\t\t\t\t\t\t\theight: $u.addUnit(itemHeight),\n\t\t\t\t\t\t\tlineHeight: $u.addUnit(itemHeight),\n\t\t\t\t\t\t\tfontWeight: index1 === innerIndex[index] ? 'bold' : 'normal'\n\t\t\t\t\t\t}\"\n\t\t\t\t\t>{{ getItemText(item1) }}</text>\n\t\t\t\t</picker-view-column>\n\t\t\t</picker-view>\n\t\t\t<view\n\t\t\t\tv-if=\"loading\"\n\t\t\t\tclass=\"u-picker--loading\"\n\t\t\t>\n\t\t\t\t<u-loading-icon mode=\"circle\"></u-loading-icon>\n\t\t\t</view>\n\t\t</view>\n\t</u-popup>\n</template>\n\n<script>\n/**\n * u-picker\n * @description 选择器\n * @property {Boolean}\t\t\tshow\t\t\t\t是否显示picker弹窗（默认 false ）\n * @property {Boolean}\t\t\tshowToolbar\t\t\t是否显示顶部的操作栏（默认 true ）\n * @property {String}\t\t\ttitle\t\t\t\t顶部标题\n * @property {Array}\t\t\tcolumns\t\t\t\t对象数组，设置每一列的数据\n * @property {Boolean}\t\t\tloading\t\t\t\t是否显示加载中状态（默认 false ）\n * @property {String | Number}\titemHeight\t\t\t各列中，单个选项的高度（默认 44 ）\n * @property {String}\t\t\tcancelText\t\t\t取消按钮的文字（默认 '取消' ）\n * @property {String}\t\t\tconfirmText\t\t\t确认按钮的文字（默认 '确定' ）\n * @property {String}\t\t\tcancelColor\t\t\t取消按钮的颜色（默认 '#909193' ）\n * @property {String}\t\t\tconfirmColor\t\t确认按钮的颜色（默认 '#3c9cff' ）\n * @property {String | Number}\tvisibleItemCount\t每列中可见选项的数量（默认 5 ）\n * @property {String}\t\t\tkeyName\t\t\t\t选项对象中，需要展示的属性键名（默认 'text' ）\n * @property {Boolean}\t\t\tcloseOnClickOverlay\t是否允许点击遮罩关闭选择器（默认 false ）\n * @property {Array}\t\t\tdefaultIndex\t\t各列的默认索引\n * @property {Boolean}\t\t\timmediateChange\t\t是否在手指松开时立即触发change事件（默认 false ）\n * @event {Function} close\t\t关闭选择器时触发\n * @event {Function} cancel\t\t点击取消按钮触发\n * @event {Function} change\t\t当选择值变化时触发\n * @event {Function} confirm\t点击确定按钮，返回当前选择的值\n */\nimport props from './props.js';\nexport default {\n\tname: 'u-picker',\n\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\tdata() {\n\t\treturn {\n\t\t\t// 上一次选择的列索引\n\t\t\tlastIndex: [],\n\t\t\t// 索引值 ，对应picker-view的value\n\t\t\tinnerIndex: [],\n\t\t\t// 各列的值\n\t\t\tinnerColumns: [],\n\t\t\t// 上一次的变化列索引\n\t\t\tcolumnIndex: 0,\n\t\t}\n\t},\n\twatch: {\n\t\t// 监听默认索引的变化，重新设置对应的值\n\t\tdefaultIndex: {\n\t\t\timmediate: true,\n\t\t\thandler(n) {\n\t\t\t\tthis.setIndexs(n, true)\n\t\t\t}\n\t\t},\n\t\t// 监听columns参数的变化\n\t\tcolumns: {\n\t\t\timmediate: true,\n\t\t\thandler(n) {\n\t\t\t\tthis.setColumns(n)\n\t\t\t}\n\t\t},\n\t},\n\tmethods: {\n\t\t// 获取item需要显示的文字，判别为对象还是文本\n\t\tgetItemText(item) {\n\t\t\tif (uni.$u.test.object(item)) {\n\t\t\t\treturn item[this.keyName]\n\t\t\t} else {\n\t\t\t\treturn item\n\t\t\t}\n\t\t},\n\t\t// 关闭选择器\n\t\tcloseHandler() {\n\t\t\tif (this.closeOnClickOverlay) {\n\t\t\t\tthis.$emit('close')\n\t\t\t}\n\t\t},\n\t\t// 点击工具栏的取消按钮\n\t\tcancel() {\n\t\t\tthis.$emit('cancel')\n\t\t},\n\t\t// 点击工具栏的确定按钮\n\t\tconfirm() {\n\t\t\tthis.$emit('confirm', {\n\t\t\t\tindexs: this.innerIndex,\n\t\t\t\tvalue: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),\n\t\t\t\tvalues: this.innerColumns\n\t\t\t})\n\t\t},\n\t\t// 选择器某一列的数据发生变化时触发\n\t\tchangeHandler(e) {\n\t\t\tconst {\n\t\t\t\tvalue\n\t\t\t} = e.detail\n\t\t\tlet index = 0,\n\t\t\t\tcolumnIndex = 0\n\t\t\t// 通过对比前后两次的列索引，得出当前变化的是哪一列\n\t\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\t\tlet item = value[i]\n\t\t\t\tif (item !== (this.lastIndex[i] || 0)) { // 把undefined转为合法假值0\n\t\t\t\t\t// 设置columnIndex为当前变化列的索引\n\t\t\t\t\tcolumnIndex = i\n\t\t\t\t\t// index则为变化列中的变化项的索引\n\t\t\t\t\tindex = item\n\t\t\t\t\tbreak // 终止循环，即使少一次循环，也是性能的提升\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.columnIndex = columnIndex\n\t\t\tconst values = this.innerColumns\n\t\t\t// 将当前的各项变化索引，设置为\"上一次\"的索引变化值\n\t\t\tthis.setLastIndex(value)\n\t\t\tthis.setIndexs(value)\n\n\t\t\tthis.$emit('change', {\n\t\t\t\t// #ifndef MP-WEIXIN || MP-LARK\n\t\t\t\t// 微信小程序不能传递this，会因为循环引用而报错\n\t\t\t\tpicker: this,\n\t\t\t\t// #endif\n\t\t\t\tvalue: this.innerColumns.map((item, index) => item[value[index]]),\n\t\t\t\tindex,\n\t\t\t\tindexs: value,\n\t\t\t\t// values为当前变化列的数组内容\n\t\t\t\tvalues,\n\t\t\t\tcolumnIndex\n\t\t\t})\n\t\t},\n\t\t// 设置index索引，此方法可被外部调用设置\n\t\tsetIndexs(index, setLastIndex) {\n\t\t\tthis.innerIndex = uni.$u.deepClone(index)\n\t\t\tif (setLastIndex) {\n\t\t\t\tthis.setLastIndex(index)\n\t\t\t}\n\t\t},\n\t\t// 记录上一次的各列索引位置\n\t\tsetLastIndex(index) {\n\t\t\t// 当能进入此方法，意味着当前设置的各列默认索引，即为“上一次”的选中值，需要记录，是因为changeHandler中\n\t\t\t// 需要拿前后的变化值进行对比，得出当前发生改变的是哪一列\n\t\t\tthis.lastIndex = uni.$u.deepClone(index)\n\t\t},\n\t\t// 设置对应列选项的所有值\n\t\tsetColumnValues(columnIndex, values) {\n\t\t\t// 替换innerColumns数组中columnIndex索引的值为values，使用的是数组的splice方法\n\t\t\tthis.innerColumns.splice(columnIndex, 1, values)\n\t\t\t// 拷贝一份原有的innerIndex做临时变量，将大于当前变化列的所有的列的默认索引设置为0\n\t\t\tlet tmpIndex = uni.$u.deepClone(this.innerIndex)\n\t\t\tfor (let i = 0; i < this.innerColumns.length; i++) {\n\t\t\t\tif (i > this.columnIndex) {\n\t\t\t\t\ttmpIndex[i] = 0\n\t\t\t\t}\n\t\t\t}\n\t\t\t// 一次性赋值，不能单个修改，否则无效\n\t\t\tthis.setIndexs(tmpIndex)\n\t\t},\n\t\t// 获取对应列的所有选项\n\t\tgetColumnValues(columnIndex) {\n\t\t\t// 进行同步阻塞，因为外部得到change事件之后，可能需要执行setColumnValues更新列的值\n\t\t\t// 索引如果在外部change的回调中调用getColumnValues的话，可能无法得到变更后的列值，这里进行一定延时，保证值的准确性\n\t\t\t(async () => {\n\t\t\t\tawait uni.$u.sleep()\n\t\t\t})()\n\t\t\treturn this.innerColumns[columnIndex]\n\t\t},\n\t\t// 设置整体各列的columns的值\n\t\tsetColumns(columns) {\n\t\t\tthis.innerColumns = uni.$u.deepClone(columns)\n\t\t\t// 如果在设置各列数据时，没有被设置默认的各列索引defaultIndex，那么用0去填充它，数组长度为列的数量\n\t\t\tif (this.innerIndex.length === 0) {\n\t\t\t\tthis.innerIndex = new Array(columns.length).fill(0)\n\t\t\t}\n\t\t},\n\t\t// 获取各列选中值对应的索引\n\t\tgetIndexs() {\n\t\t\treturn this.innerIndex\n\t\t},\n\t\t// 获取各列选中的值\n\t\tgetValues() {\n\t\t\t// 进行同步阻塞，因为外部得到change事件之后，可能需要执行setColumnValues更新列的值\n\t\t\t// 索引如果在外部change的回调中调用getValues的话，可能无法得到变更后的列值，这里进行一定延时，保证值的准确性\n\t\t\t(async () => {\n\t\t\t\tawait uni.$u.sleep()\n\t\t\t})()\n\t\t\treturn this.innerColumns.map((item, index) => item[this.innerIndex[index]])\n\t\t}\n\t},\n}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-picker {\n\t\tposition: relative;\n\n\t\t&__view {\n\n\t\t\t&__column {\n\t\t\t\t@include flex;\n\t\t\t\tflex: 1;\n\t\t\t\tjustify-content: center;\n\n\t\t\t\t&__item {\n\t\t\t\t\t@include flex;\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tfont-size: 16px;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t\t/* #ifndef APP-NVUE */\n\t\t\t\t\tdisplay: block;\n\t\t\t\t\t/* #endif */\n\t\t\t\t\tcolor: $u-main-color;\n\n\t\t\t\t\t&--disabled {\n\t\t\t\t\t\t/* #ifndef APP-NVUE */\n\t\t\t\t\t\tcursor: not-allowed;\n\t\t\t\t\t\t/* #endif */\n\t\t\t\t\t\topacity: 0.35;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&--loading {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tright: 0;\n\t\t\tleft: 0;\n\t\t\tbottom: 0;\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\tbackground-color: rgba(255, 255, 255, 0.87);\n\t\t\tz-index: 1000;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-picker-column/props.js",
    "content": "export default {\n    props: {\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-picker-column/u-picker-column.vue",
    "content": "<template>\n\t<picker-view-column>\n\t\t<view class=\"u-picker-column\">\n\n\t\t</view>\n\t</picker-view-column>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * PickerColumn \n\t * @description \n\t * @tutorial url\n\t * @property {String}\n\t * @event {Function}\n\t * @example\n\t */\n\texport default {\n\t\tname: 'u-picker-column',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-popup/props.js",
    "content": "export default {\n    props: {\n        // 是否展示弹窗\n        show: {\n            type: Boolean,\n            default: uni.$u.props.popup.show\n        },\n        // 是否显示遮罩\n        overlay: {\n            type: Boolean,\n            default: uni.$u.props.popup.overlay\n        },\n        // 弹出的方向，可选值为 top bottom right left center\n        mode: {\n            type: String,\n            default: uni.$u.props.popup.mode\n        },\n        // 动画时长，单位ms\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.popup.duration\n        },\n        // 是否显示关闭图标\n        closeable: {\n            type: Boolean,\n            default: uni.$u.props.popup.closeable\n        },\n        // 自定义遮罩的样式\n        overlayStyle: {\n            type: [Object, String],\n            default: uni.$u.props.popup.overlayStyle\n        },\n        // 点击遮罩是否关闭弹窗\n        closeOnClickOverlay: {\n            type: Boolean,\n            default: uni.$u.props.popup.closeOnClickOverlay\n        },\n        // 层级\n        zIndex: {\n            type: [String, Number],\n            default: uni.$u.props.popup.zIndex\n        },\n        // 是否为iPhoneX留出底部安全距离\n        safeAreaInsetBottom: {\n            type: Boolean,\n            default: uni.$u.props.popup.safeAreaInsetBottom\n        },\n        // 是否留出顶部安全距离（状态栏高度）\n        safeAreaInsetTop: {\n            type: Boolean,\n            default: uni.$u.props.popup.safeAreaInsetTop\n        },\n        // 自定义关闭图标位置，top-left为左上角，top-right为右上角，bottom-left为左下角，bottom-right为右下角\n        closeIconPos: {\n            type: String,\n            default: uni.$u.props.popup.closeIconPos\n        },\n        // 是否显示圆角\n        round: {\n            type: [Boolean, String, Number],\n            default: uni.$u.props.popup.round\n        },\n        // mode=center，也即中部弹出时，是否使用缩放模式\n        zoom: {\n            type: Boolean,\n            default: uni.$u.props.popup.zoom\n        },\n        // 弹窗背景色，设置为transparent可去除白色背景\n        bgColor: {\n            type: String,\n            default: uni.$u.props.popup.bgColor\n        },\n        // 遮罩的透明度，0-1之间\n        overlayOpacity: {\n            type: [Number, String],\n            default: uni.$u.props.popup.overlayOpacity\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-popup/u-popup.vue",
    "content": "<template>\n\t<view class=\"u-popup\">\n\t\t<u-overlay\n\t\t\t:show=\"show\"\n\t\t\t@click=\"overlayClick\"\n\t\t\tv-if=\"overlay\"\n\t\t\t:duration=\"overlayDuration\"\n\t\t\t:customStyle=\"overlayStyle\"\n\t\t\t:opacity=\"overlayOpacity\"\n\t\t></u-overlay>\n\t\t<u-transition\n\t\t\t:show=\"show\"\n\t\t\t:customStyle=\"transitionStyle\"\n\t\t\t:mode=\"position\"\n\t\t\t:duration=\"duration\"\n\t\t\t@afterEnter=\"afterEnter\"\n\t\t\t@click=\"clickHandler\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-popup__content\"\n\t\t\t\t:style=\"[contentStyle]\"\n\t\t\t\t@tap.stop=\"noop\"\n\t\t\t>\n\t\t\t\t<u-status-bar v-if=\"safeAreaInsetTop\"></u-status-bar>\n\t\t\t\t<slot></slot>\n\t\t\t\t<view\n\t\t\t\t\tv-if=\"closeable\"\n\t\t\t\t\t@tap.stop=\"close\"\n\t\t\t\t\tclass=\"u-popup__content__close\"\n\t\t\t\t\t:class=\"['u-popup__content__close--' + closeIconPos]\"\n\t\t\t\t\thover-class=\"u-popup__content__close--hover\"\n\t\t\t\t\thover-stay-time=\"150\"\n\t\t\t\t>\n\t\t\t\t\t<u-icon\n\t\t\t\t\t\tname=\"close\"\n\t\t\t\t\t\tcolor=\"#909399\"\n\t\t\t\t\t\tsize=\"18\"\n\t\t\t\t\t\tbold\n\t\t\t\t\t></u-icon>\n\t\t\t\t</view>\n\t\t\t\t<u-safe-bottom v-if=\"safeAreaInsetBottom\"></u-safe-bottom>\n\t\t\t</view>\n\t\t</u-transition>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * popup 弹窗\n\t * @description 弹出层容器，用于展示弹窗、信息提示等内容，支持上、下、左、右和中部弹出。组件只提供容器，内部内容由用户自定义\n\t * @tutorial https://www.uviewui.com/components/popup.html\n\t * @property {Boolean}\t\t\tshow\t\t\t\t是否展示弹窗 (默认 false )\n\t * @property {Boolean}\t\t\toverlay\t\t\t\t是否显示遮罩 （默认 true ）\n\t * @property {String}\t\t\tmode\t\t\t\t弹出方向（默认 'bottom' ）\n\t * @property {String | Number}\tduration\t\t\t动画时长，单位ms （默认 300 ）\n\t * @property {String | Number}\toverlayDuration\t\t\t遮罩层动画时长，单位ms （默认 350 ）\n\t * @property {Boolean}\t\t\tcloseable\t\t\t是否显示关闭图标（默认 false ）\n\t * @property {Object | String}\toverlayStyle\t\t自定义遮罩的样式\n\t * @property {String | Number}\toverlayOpacity\t\t遮罩透明度，0-1之间（默认 0.5）\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t点击遮罩是否关闭弹窗 （默认  true ）\n\t * @property {String | Number}\tzIndex\t\t\t\t层级 （默认 10075 ）\n\t * @property {Boolean}\t\t\tsafeAreaInsetBottom\t是否为iPhoneX留出底部安全距离 （默认 true ）\n\t * @property {Boolean}\t\t\tsafeAreaInsetTop\t是否留出顶部安全距离（状态栏高度） （默认 false ）\n\t * @property {String}\t\t\tcloseIconPos\t\t自定义关闭图标位置（默认 'top-right' ）\n\t * @property {String | Number}\tround\t\t\t\t圆角值（默认 0）\n\t * @property {Boolean}\t\t\tzoom\t\t\t\t当mode=center时 是否开启缩放（默认 true ）\n\t * @property {Object}\t\t\tcustomStyle\t\t\t组件的样式，对象形式\n\t * @event {Function} open 弹出层打开\n\t * @event {Function} close 弹出层收起\n\t * @example <u-popup v-model=\"show\"><text>出淤泥而不染，濯清涟而不妖</text></u-popup>\n\t */\n\texport default {\n\t\tname: 'u-popup',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\toverlayDuration: this.duration + 50\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tshow(newValue, oldValue) {\n\t\t\t\tif (newValue === true) {\n\t\t\t\t\t// #ifdef MP-WEIXIN\n\t\t\t\t\tconst children = this.$children\n\t\t\t\t\tthis.retryComputedComponentRect(children)\n\t\t\t\t\t// #endif\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\ttransitionStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tzIndex: this.zIndex,\n\t\t\t\t\tposition: 'fixed',\n\t\t\t\t\tdisplay: 'flex',\n\t\t\t\t}\n\t\t\t\tstyle[this.mode] = 0\n\t\t\t\tif (this.mode === 'left') {\n\t\t\t\t\treturn uni.$u.deepMerge(style, {\n\t\t\t\t\t\tbottom: 0,\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t})\n\t\t\t\t} else if (this.mode === 'right') {\n\t\t\t\t\treturn uni.$u.deepMerge(style, {\n\t\t\t\t\t\tbottom: 0,\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t})\n\t\t\t\t} else if (this.mode === 'top') {\n\t\t\t\t\treturn uni.$u.deepMerge(style, {\n\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\tright: 0\n\t\t\t\t\t})\n\t\t\t\t} else if (this.mode === 'bottom') {\n\t\t\t\t\treturn uni.$u.deepMerge(style, {\n\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\tright: 0,\n\t\t\t\t\t})\n\t\t\t\t} else if (this.mode === 'center') {\n\t\t\t\t\treturn uni.$u.deepMerge(style, {\n\t\t\t\t\t\talignItems: 'center',\n\t\t\t\t\t\t'justify-content': 'center',\n\t\t\t\t\t\ttop: 0,\n\t\t\t\t\t\tleft: 0,\n\t\t\t\t\t\tright: 0,\n\t\t\t\t\t\tbottom: 0\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t\tcontentStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\t// 通过设备信息的safeAreaInsets值来判断是否需要预留顶部状态栏和底部安全局的位置\n\t\t\t\t// 不使用css方案，是因为nvue不支持css的iPhoneX安全区查询属性\n\t\t\t\tconst {\n\t\t\t\t\tsafeAreaInsets\n\t\t\t\t} = uni.$u.sys()\n\t\t\t\tif (this.mode !== 'center') {\n\t\t\t\t\tstyle.flex = 1\n\t\t\t\t}\n\t\t\t\t// 背景色，一般用于设置为transparent，去除默认的白色背景\n\t\t\t\tif (this.bgColor) {\n\t\t\t\t\tstyle.backgroundColor = this.bgColor\n\t\t\t\t}\n\t\t\t\tif(this.round) {\n\t\t\t\t\tconst value = uni.$u.addUnit(this.round)\n\t\t\t\t\tif(this.mode === 'top') {\n\t\t\t\t\t\tstyle.borderBottomLeftRadius = value\n\t\t\t\t\t\tstyle.borderBottomRightRadius = value\n\t\t\t\t\t} else if(this.mode === 'bottom') {\n\t\t\t\t\t\tstyle.borderTopLeftRadius = value\n\t\t\t\t\t\tstyle.borderTopRightRadius = value\n\t\t\t\t\t} else if(this.mode === 'center') {\n\t\t\t\t\t\tstyle.borderRadius = value\n\t\t\t\t\t} \n\t\t\t\t}\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t},\n\t\t\tposition() {\n\t\t\t\tif (this.mode === 'center') {\n\t\t\t\t\treturn this.zoom ? 'fade-zoom' : 'fade'\n\t\t\t\t}\n\t\t\t\tif (this.mode === 'left') {\n\t\t\t\t\treturn 'slide-left'\n\t\t\t\t}\n\t\t\t\tif (this.mode === 'right') {\n\t\t\t\t\treturn 'slide-right'\n\t\t\t\t}\n\t\t\t\tif (this.mode === 'bottom') {\n\t\t\t\t\treturn 'slide-up'\n\t\t\t\t}\n\t\t\t\tif (this.mode === 'top') {\n\t\t\t\t\treturn 'slide-down'\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击遮罩\n\t\t\toverlayClick() {\n\t\t\t\tif (this.closeOnClickOverlay) {\n\t\t\t\t\tthis.$emit('close')\n\t\t\t\t}\n\t\t\t},\n\t\t\tclose(e) {\n\t\t\t\tthis.$emit('close')\n\t\t\t},\n\t\t\tafterEnter() {\n\t\t\t\tthis.$emit('open')\n\t\t\t},\n\t\t\tclickHandler() {\n\t\t\t\t// 由于中部弹出时，其u-transition占据了整个页面相当于遮罩，此时需要发出遮罩点击事件，是否无法通过点击遮罩关闭弹窗\n\t\t\t\tif(this.mode === 'center') {\n\t\t\t\t\tthis.overlayClick()\n\t\t\t\t}\n\t\t\t\tthis.$emit('click')\n\t\t\t},\n\t\t\t// #ifdef MP-WEIXIN\n\t\t\tretryComputedComponentRect(children) {\n\t\t\t\t// 组件内部需要计算节点的组件\n\t\t\t\tconst names = ['u-calendar-month', 'u-album', 'u-collapse-item', 'u-dropdown', 'u-index-item', 'u-index-list',\n\t\t\t\t\t'u-line-progress', 'u-list-item', 'u-rate', 'u-read-more', 'u-row', 'u-row-notice', 'u-scroll-list',\n\t\t\t\t\t'u-skeleton', 'u-slider', 'u-steps-item', 'u-sticky', 'u-subsection', 'u-swipe-action-item', 'u-tabbar',\n\t\t\t\t\t'u-tabs', 'u-tooltip'\n\t\t\t\t]\n\t\t\t\t// 历遍所有的子组件节点\n\t\t\t\tfor (let i = 0; i < children.length; i++) {\n\t\t\t\t\tconst child = children[i]\n\t\t\t\t\t// 拿到子组件的子组件\n\t\t\t\t\tconst grandChild = child.$children\n\t\t\t\t\t// 判断如果在需要重新初始化的组件数组中名中，并且存在init方法的话，则执行\n\t\t\t\t\tif (names.includes(child.$options.name) && typeof child?.init === 'function') {\n\t\t\t\t\t\t// 需要进行一定的延时，因为初始化页面需要时间\n\t\t\t\t\t\tuni.$u.sleep(50).then(() => {\n\t\t\t\t\t\t\tchild.init()\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 (grandChild.length) {\n\t\t\t\t\t\tthis.retryComputedComponentRect(grandChild)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// #endif\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-popup-flex:1 !default;\n\t$u-popup-content-background-color: #fff !default;\n\n\t.u-popup {\n\t\tflex: $u-popup-flex;\n\n\t\t&__content {\n\t\t\tbackground-color: $u-popup-content-background-color;\n\t\t\tposition: relative;\n\n\t\t\t&--round-top {\n\t\t\t\tborder-top-left-radius: 0;\n\t\t\t\tborder-top-right-radius: 0;\n\t\t\t\tborder-bottom-left-radius: 10px;\n\t\t\t\tborder-bottom-right-radius: 10px;\n\t\t\t}\n\n\t\t\t&--round-left {\n\t\t\t\tborder-top-left-radius: 0;\n\t\t\t\tborder-top-right-radius: 10px;\n\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t\tborder-bottom-right-radius: 10px;\n\t\t\t}\n\n\t\t\t&--round-right {\n\t\t\t\tborder-top-left-radius: 10px;\n\t\t\t\tborder-top-right-radius: 0;\n\t\t\t\tborder-bottom-left-radius: 10px;\n\t\t\t\tborder-bottom-right-radius: 0;\n\t\t\t}\n\n\t\t\t&--round-bottom {\n\t\t\t\tborder-top-left-radius: 10px;\n\t\t\t\tborder-top-right-radius: 10px;\n\t\t\t\tborder-bottom-left-radius: 0;\n\t\t\t\tborder-bottom-right-radius: 0;\n\t\t\t}\n\n\t\t\t&--round-center {\n\t\t\t\tborder-top-left-radius: 10px;\n\t\t\t\tborder-top-right-radius: 10px;\n\t\t\t\tborder-bottom-left-radius: 10px;\n\t\t\t\tborder-bottom-right-radius: 10px;\n\t\t\t}\n\n\t\t\t&__close {\n\t\t\t\tposition: absolute;\n\n\t\t\t\t&--hover {\n\t\t\t\t\topacity: 0.4;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&__close--top-left {\n\t\t\t\ttop: 15px;\n\t\t\t\tleft: 15px;\n\t\t\t}\n\n\t\t\t&__close--top-right {\n\t\t\t\ttop: 15px;\n\t\t\t\tright: 15px;\n\t\t\t}\n\n\t\t\t&__close--bottom-left {\n\t\t\t\tbottom: 15px;\n\t\t\t\tleft: 15px;\n\t\t\t}\n\n\t\t\t&__close--bottom-right {\n\t\t\t\tright: 15px;\n\t\t\t\tbottom: 15px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-radio/props.js",
    "content": "export default {\n    props: {\n        // radio的名称\n        name: {\n            type: [String, Number, Boolean],\n            default: uni.$u.props.radio.name\n        },\n        // 形状，square为方形，circle为圆型\n        shape: {\n            type: String,\n            default: uni.$u.props.radio.shape\n        },\n        // 是否禁用\n        disabled: {\n            type: [String, Boolean],\n            default: uni.$u.props.radio.disabled\n        },\n        // 是否禁止点击提示语选中单选框\n        labelDisabled: {\n            type: [String, Boolean],\n            default: uni.$u.props.radio.labelDisabled\n        },\n        // 选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\n        activeColor: {\n            type: String,\n            default: uni.$u.props.radio.activeColor\n        },\n        // 未选中的颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.radio.inactiveColor\n        },\n        // 图标的大小，单位px\n        iconSize: {\n            type: [String, Number],\n            default: uni.$u.props.radio.iconSize\n        },\n        // label的字体大小，px单位\n        labelSize: {\n            type: [String, Number],\n            default: uni.$u.props.radio.labelSize\n        },\n        // label提示文字，因为nvue下，直接slot进来的文字，由于特殊的结构，无法修改样式\n        label: {\n            type: [String, Number],\n            default: uni.$u.props.radio.label\n        },\n        // 整体的大小\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.radio.size\n        },\n        // 图标颜色\n        color: {\n            type: String,\n            default: uni.$u.props.radio.color\n        },\n        // label的颜色\n        labelColor: {\n            type: String,\n            default: uni.$u.props.radio.labelColor\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-radio/u-radio.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-radio\"\n\t\t@tap.stop=\"wrapperClickHandler\"\n\t    :style=\"[radioStyle]\"\n\t    :class=\"[`u-radio-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']\"\n\t>\n\t\t<view\n\t\t    class=\"u-radio__icon-wrap\"\n\t\t    @tap.stop=\"iconClickHandler\"\n\t\t    :class=\"iconClasses\"\n\t\t    :style=\"[iconWrapStyle]\"\n\t\t>\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<u-icon\n\t\t\t\t    class=\"u-radio__icon-wrap__icon\"\n\t\t\t\t    name=\"checkbox-mark\"\n\t\t\t\t    :size=\"elIconSize\"\n\t\t\t\t    :color=\"elIconColor\"\n\t\t\t\t/>\n\t\t\t</slot>\n\t\t</view>\n\t\t<slot>\n\t\t\t<text\n\t\t\t\tclass=\"u-radio__text\"\n\t\t\t\t@tap.stop=\"labelClickHandler\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tcolor: elDisabled ? elInactiveColor : elLabelColor,\n\t\t\t\t\tfontSize: elLabelSize,\n\t\t\t\t\tlineHeight: elLabelSize\n\t\t\t\t}\"\n\t\t\t>{{label}}</text>\n\t\t</slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * radio 单选框\n\t * @description 单选框用于有一个选择，用户只能选择其中一个的场景。搭配u-radio-group使用\n\t * @tutorial https://www.uviewui.com/components/radio.html\n\t * @property {String | Number}\tname\t\t\tradio的名称\n\t * @property {String}\t\t\tshape\t\t\t形状，square为方形，circle为圆型\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用\n\t * @property {String | Boolean}\tlabelDisabled\t是否禁止点击提示语选中单选框\n\t * @property {String}\t\t\tactiveColor\t\t选中时的颜色，如设置parent的active-color将失效\n\t * @property {String}\t\t\tinactiveColor\t未选中的颜色\n\t * @property {String | Number}\ticonSize\t\t图标大小，单位px\n\t * @property {String | Number}\tlabelSize\t\tlabel字体大小，单位px\n\t * @property {String | Number}\tlabel\t\t\tlabel提示文字，因为nvue下，直接slot进来的文字，由于特殊的结构，无法修改样式\n\t * @property {String | Number}\tsize\t\t\t整体的大小\n\t * @property {String}\t\t\ticonColor\t\t图标颜色\n\t * @property {String}\t\t\tlabelColor\t\tlabel的颜色\n\t * @property {Object}\t\t\tcustomStyle\t\t组件的样式，对象形式\n\t * \n\t * @event {Function} change 某个radio状态发生变化时触发(选中状态)\n\t * @example <u-radio :labelDisabled=\"false\">门掩黄昏，无计留春住</u-radio>\n\t */\n\texport default {\n\t\tname: \"u-radio\",\n\t\t\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tchecked: false,\n\t\t\t\t// 当你看到这段代码的时候，\n\t\t\t\t// 父组件的默认值，因为头条小程序不支持在computed中使用this.parent.shape的形式\n\t\t\t\t// 故只能使用如此方法\n\t\t\t\tparentData: {\n\t\t\t\t\ticonSize: 12,\n\t\t\t\t\tlabelDisabled: null,\n\t\t\t\t\tdisabled: null,\n\t\t\t\t\tshape: null,\n\t\t\t\t\tactiveColor: null,\n\t\t\t\t\tinactiveColor: null,\n\t\t\t\t\tsize: 18,\n\t\t\t\t\tvalue: null,\n\t\t\t\t\ticonColor: null,\n\t\t\t\t\tplacement: 'row',\n\t\t\t\t\tborderBottom: false,\n\t\t\t\t\ticonPlacement: 'left'\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 是否禁用，如果父组件u-raios-group禁用的话，将会忽略子组件的配置\n\t\t\telDisabled() {\n\t\t\t\treturn this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;\n\t\t\t},\n\t\t\t// 是否禁用label点击\n\t\t\telLabelDisabled() {\n\t\t\t\treturn this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :\n\t\t\t\t\tfalse;\n\t\t\t},\n\t\t\t// 组件尺寸，对应size的值，默认值为21px\n\t\t\telSize() {\n\t\t\t\treturn this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);\n\t\t\t},\n\t\t\t// 组件的勾选图标的尺寸，默认12px\n\t\t\telIconSize() {\n\t\t\t\treturn this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);\n\t\t\t},\n\t\t\t// 组件选中激活时的颜色\n\t\t\telActiveColor() {\n\t\t\t\treturn this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');\n\t\t\t},\n\t\t\t// 组件选未中激活时的颜色\n\t\t\telInactiveColor() {\n\t\t\t\treturn this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :\n\t\t\t\t\t'#c8c9cc');\n\t\t\t},\n\t\t\t// label的颜色\n\t\t\telLabelColor() {\n\t\t\t\treturn this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')\n\t\t\t},\n\t\t\t// 组件的形状\n\t\t\telShape() {\n\t\t\t\treturn this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');\n\t\t\t},\n\t\t\t// label大小\n\t\t\telLabelSize() {\n\t\t\t\treturn uni.$u.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :\n\t\t\t\t\t'15'))\n\t\t\t},\n\t\t\telIconColor() {\n\t\t\t\tconst iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :\n\t\t\t\t\t'#ffffff');\n\t\t\t\t// 图标的颜色\n\t\t\t\tif (this.elDisabled) {\n\t\t\t\t\t// disabled状态下，已勾选的radio图标改为elInactiveColor\n\t\t\t\t\treturn this.checked ? this.elInactiveColor : 'transparent'\n\t\t\t\t} else {\n\t\t\t\t\treturn this.checked ? iconColor : 'transparent'\n\t\t\t\t}\n\t\t\t},\n\t\t\ticonClasses() {\n\t\t\t\tlet classes = []\n\t\t\t\t// 组件的形状\n\t\t\t\tclasses.push('u-radio__icon-wrap--' + this.elShape)\n\t\t\t\tif (this.elDisabled) {\n\t\t\t\t\tclasses.push('u-radio__icon-wrap--disabled')\n\t\t\t\t}\n\t\t\t\tif (this.checked && this.elDisabled) {\n\t\t\t\t\tclasses.push('u-radio__icon-wrap--disabled--checked')\n\t\t\t\t}\n\t\t\t\t// 支付宝，头条小程序无法动态绑定一个数组类名，否则解析出来的结果会带有\",\"，而导致失效\n\t\t\t\t// #ifdef MP-ALIPAY || MP-TOUTIAO\n\t\t\t\tclasses = classes.join(' ')\n\t\t\t\t// #endif\n\t\t\t\treturn classes\n\t\t\t},\n\t\t\ticonWrapStyle() {\n\t\t\t\t// radio的整体样式\n\t\t\t\tconst style = {}\n\t\t\t\tstyle.backgroundColor = this.checked && !this.elDisabled ? this.elActiveColor : '#ffffff'\n\t\t\t\tstyle.borderColor = this.checked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor\n\t\t\t\tstyle.width = uni.$u.addUnit(this.elSize)\n\t\t\t\tstyle.height = uni.$u.addUnit(this.elSize)\n\t\t\t\t// 如果是图标在右边的话，移除它的右边距\n\t\t\t\tif (this.parentData.iconPlacement === 'right') {\n\t\t\t\t\tstyle.marginRight = 0\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tradioStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tif(this.parentData.borderBottom && this.parentData.placement === 'row') {\n\t\t\t\t\tuni.$u.error('检测到您将borderBottom设置为true，需要同时将u-radio-group的placement设置为column才有效')\n\t\t\t\t}\n\t\t\t\t// 当父组件设置了显示下边框并且排列形式为纵向时，给内容和边框之间加上一定间隔\n\t\t\t\tif(this.parentData.borderBottom && this.parentData.placement === 'column') {\n\t\t\t\t\t// ios像素密度高，需要多一点的距离\n\t\t\t\t\tstyle.paddingBottom = uni.$u.os() === 'ios' ? '12px' : '8px'\n\t\t\t\t}\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 支付宝小程序不支持provide/inject，所以使用这个方法获取整个父组件，在created定义，避免循环引用\n\t\t\t\tthis.updateParentData()\n\t\t\t\tif (!this.parent) {\n\t\t\t\t\tuni.$u.error('u-radio必须搭配u-radio-group组件使用')\n\t\t\t\t}\n\t\t\t\t// 设置初始化时，是否默认选中的状态\n\t\t\t\tthis.checked = this.name === this.parentData.value\n\t\t\t},\n\t\t\tupdateParentData() {\n\t\t\t\tthis.getParentData('u-radio-group')\n\t\t\t},\n\t\t\t// 点击图标\n\t\t\ticonClickHandler(e) {\n\t\t\t\tthis.preventEvent(e)\n\t\t\t\t// 如果整体被禁用，不允许被点击\n\t\t\t\tif (!this.elDisabled) {\n\t\t\t\t\tthis.setRadioCheckedStatus()\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 横向两端排列时，点击组件即可触发选中事件\n\t\t\twrapperClickHandler(e) {\n\t\t\t\tthis.parentData.iconPlacement === 'right' && this.iconClickHandler(e)\n\t\t\t},\n\t\t\t// 点击label\n\t\t\tlabelClickHandler(e) {\n\t\t\t\tthis.preventEvent(e)\n\t\t\t\t// 如果按钮整体被禁用或者label被禁用，则不允许点击文字修改状态\n\t\t\t\tif (!this.elLabelDisabled && !this.elDisabled) {\n\t\t\t\t\tthis.setRadioCheckedStatus()\n\t\t\t\t}\n\t\t\t},\n\t\t\temitEvent() {\n\t\t\t\t// u-radio的checked不为true时(意味着未选中)，才发出事件，避免多次点击触发事件\n\t\t\t\tif (!this.checked) {\n\t\t\t\t\tthis.$emit('change', this.name)\n\t\t\t\t\t// 尝试调用u-form的验证方法，进行一定延迟，否则微信小程序更新可能会不及时\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tuni.$u.formValidate(this, 'change')\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 改变组件选中状态\n\t\t\t// 这里的改变的依据是，更改本组件的checked值为true，同时通过父组件遍历所有u-radio实例\n\t\t\t// 将本组件外的其他u-radio的checked都设置为false(都被取消选中状态)，因而只剩下一个为选中状态\n\t\t\tsetRadioCheckedStatus() {\n\t\t\t\tthis.emitEvent()\n\t\t\t\t// 将本组件标记为选中状态\n\t\t\t\tthis.checked = true\n\t\t\t\ttypeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t$u-radio-wrap-margin-right:6px !default;\n\t$u-radio-wrap-font-size:20px !default;\n\t$u-radio-wrap-border-width:1px !default;\n\t$u-radio-wrap-border-color: #c8c9cc !default;\n\t$u-radio-line-height:0 !default;\n\t$u-radio-circle-border-radius:100% !default;\n\t$u-radio-square-border-radius:3px !default;\n\t$u-radio-checked-color:#fff !default;\n\t$u-radio-checked-background-color:red !default;\n\t$u-radio-checked-border-color: #2979ff !default;\n\t$u-radio-disabled-background-color:#ebedf0 !default;\n\t$u-radio-disabled--checked-color:#c8c9cc !default;\n\t$u-radio-label-margin-left: 5px !default;\n\t$u-radio-label-margin-right:12px !default;\n\t$u-radio-label-color:$u-content-color !default;\n\t$u-radio-label-font-size:15px !default;\n\t$u-radio-label-disabled-color:#c8c9cc !default;\n\t\n\t.u-radio {\n\t\t/* #ifndef APP-NVUE */\n\t\t@include flex(row);\n\t\t/* #endif */\n\t\toverflow: hidden;\n\t\tflex-direction: row;\n\t\talign-items: center;\n\n\t\t&-label--left {\n\t\t\tflex-direction: row\n\t\t}\n\n\t\t&-label--right {\n\t\t\tflex-direction: row-reverse;\n\t\t\tjustify-content: space-between\n\t\t}\n\n\t\t&__icon-wrap {\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tbox-sizing: border-box;\n\t\t\t// nvue下，border-color过渡有问题\n\t\t\ttransition-property: border-color, background-color, color;\n\t\t\ttransition-duration: 0.2s;\n\t\t\t/* #endif */\n\t\t\tcolor: $u-content-color;\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tcolor: transparent;\n\t\t\ttext-align: center;\n\t\t\tmargin-right: $u-radio-wrap-margin-right;\n\t\t\tfont-size: $u-radio-wrap-font-size;\n\t\t\tborder-width: $u-radio-wrap-border-width;\n\t\t\tborder-color: $u-radio-wrap-border-color;\n\t\t\tborder-style: solid;\n\n\t\t\t/* #ifdef MP-TOUTIAO */\n\t\t\t// 头条小程序兼容性问题，需要设置行高为0，否则图标偏下\n\t\t\t&__icon {\n\t\t\t\tline-height: $u-radio-line-height;\n\t\t\t}\n\n\t\t\t/* #endif */\n\n\t\t\t&--circle {\n\t\t\t\tborder-radius: $u-radio-circle-border-radius;\n\t\t\t}\n\n\t\t\t&--square {\n\t\t\t\tborder-radius: $u-radio-square-border-radius;\n\t\t\t}\n\n\t\t\t&--checked {\n\t\t\t\tcolor: $u-radio-checked-color;\n\t\t\t\tbackground-color: $u-radio-checked-background-color;\n\t\t\t\tborder-color: $u-radio-checked-border-color;\n\t\t\t}\n\n\t\t\t&--disabled {\n\t\t\t\tbackground-color: $u-radio-disabled-background-color !important;\n\t\t\t}\n\n\t\t\t&--disabled--checked {\n\t\t\t\tcolor: $u-radio-disabled--checked-color !important;\n\t\t\t}\n\t\t}\n\n\t\t&__label {\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tword-wrap: break-word;\n\t\t\t/* #endif */\n\t\t\tmargin-left: $u-radio-label-margin-left;\n\t\t\tmargin-right: $u-radio-label-margin-right;\n\t\t\tcolor: $u-radio-label-color;\n\t\t\tfont-size: $u-radio-label-font-size;\n\n\t\t\t&--disabled {\n\t\t\t\tcolor: $u-radio-label-disabled-color;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-radio-group/props.js",
    "content": "export default {\n    props: {\n        // 绑定的值\n        value: {\n            type: [String, Number, Boolean],\n            default: uni.$u.props.radioGroup.value\n        },\n\n        // 是否禁用全部radio\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.radioGroup.disabled\n        },\n        // 形状，circle-圆形，square-方形\n        shape: {\n            type: String,\n            default: uni.$u.props.radioGroup.shape\n        },\n        // 选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\n        activeColor: {\n            type: String,\n            default: uni.$u.props.radioGroup.activeColor\n        },\n        // 未选中的颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.radioGroup.inactiveColor\n        },\n        // 标识符\n        name: {\n            type: String,\n            default: uni.$u.props.radioGroup.name\n        },\n        // 整个组件的尺寸，默认px\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.radioGroup.size\n        },\n        // 布局方式，row-横向，column-纵向\n        placement: {\n            type: String,\n            default: uni.$u.props.radioGroup.placement\n        },\n        // label的文本\n        label: {\n            type: [String],\n            default: uni.$u.props.radioGroup.label\n        },\n        // label的颜色 （默认 '#303133' ）\n        labelColor: {\n            type: [String],\n            default: uni.$u.props.radioGroup.labelColor\n        },\n        // label的字体大小，px单位\n        labelSize: {\n            type: [String, Number],\n            default: uni.$u.props.radioGroup.labelSize\n        },\n        // 是否禁止点击文本操作checkbox(默认 false )\n        labelDisabled: {\n            type: Boolean,\n            default: uni.$u.props.radioGroup.labelDisabled\n        },\n        // 图标颜色\n        iconColor: {\n            type: String,\n            default: uni.$u.props.radioGroup.iconColor\n        },\n        // 图标的大小，单位px\n        iconSize: {\n            type: [String, Number],\n            default: uni.$u.props.radioGroup.iconSize\n        },\n        // 竖向配列时，是否显示下划线\n        borderBottom: {\n            type: Boolean,\n            default: uni.$u.props.radioGroup.borderBottom\n        },\n        // 图标与文字的对齐方式\n        iconPlacement: {\n            type: String,\n            default: uni.$u.props.radio.iconPlacement\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-radio-group/u-radio-group.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-radio-group\"\n\t    :class=\"bemClass\"\n\t>\n\t\t<slot></slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * radioRroup 单选框父组件\n\t * @description 单选框用于有一个选择，用户只能选择其中一个的场景。搭配u-radio使用\n\t * @tutorial https://www.uviewui.com/components/radio.html\n\t * @property {String | Number | Boolean}\tvalue \t\t\t绑定的值\n\t * @property {Boolean}\t\t\t\t\t\tdisabled\t\t是否禁用所有radio（默认 false ）\n\t * @property {String}\t\t\t\t\t\tshape\t\t\t外观形状，shape-方形，circle-圆形(默认 circle )\n\t * @property {String}\t\t\t\t\t\tactiveColor\t\t选中时的颜色，应用到所有子Radio组件（默认 '#2979ff' ）\n\t * @property {String}\t\t\t\t\t\tinactiveColor\t未选中的颜色 (默认 '#c8c9cc' )\n\t * @property {String}\t\t\t\t\t\tname\t\t\t标识符\n\t * @property {String | Number}\t\t\t\tsize\t\t\t组件整体的大小，单位px（默认 18 ）\n\t * @property {String}\t\t\t\t\t\tplacement\t\t布局方式，row-横向，column-纵向 （默认 'row' ）\n\t * @property {String}\t\t\t\t\t\tlabel\t\t\t文本\n\t * @property {String}\t\t\t\t\t\tlabelColor\t\tlabel的颜色 （默认 '#303133' ）\n\t * @property {String | Number}\t\t\t\tlabelSize\t\tlabel的字体大小，px单位 （默认 14 ）\n\t * @property {Boolean}\t\t\t\t\t\tlabelDisabled\t是否禁止点击文本操作checkbox(默认 false )\n\t * @property {String}\t\t\t\t\t\ticonColor\t\t图标颜色 （默认 '#ffffff' ）\n\t * @property {String | Number}\t\t\t\ticonSize\t\t图标的大小，单位px （默认 12 ）\n\t * @property {Boolean}\t\t\t\t\t\tborderBottom\tplacement为row时，是否显示下边框 （默认 false ）\n\t * @property {String}\t\t\t\t\t\ticonPlacement\t图标与文字的对齐方式 （默认 'left' ）\n     * @property {Object}\t\t\t\t\t\tcustomStyle\t\t组件的样式，对象形式\n\t * @event {Function} change 任一个radio状态发生变化时触发\n\t * @example <u-radio-group v-model=\"value\"></u-radio-group>\n\t */\n\texport default {\n\t\tname: 'u-radio-group',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tcomputed: {\n\t\t\t// 这里computed的变量，都是子组件u-radio需要用到的，由于头条小程序的兼容性差异，子组件无法实时监听父组件参数的变化\n\t\t\t// 所以需要手动通知子组件，这里返回一个parentData变量，供watch监听，在其中去通知每一个子组件重新从父组件(u-radio-group)\n\t\t\t// 拉取父组件新的变化后的参数\n\t\t\tparentData() {\n\t\t\t\treturn [this.value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,\n\t\t\t\t\tthis.iconSize, this.borderBottom, this.placement\n\t\t\t\t]\n\t\t\t},\n\t\t\tbemClass() {\n\t\t\t\t// this.bem为一个computed变量，在mixin中\n\t\t\t\treturn this.bem('radio-group', ['placement'])\n\t\t\t},\n\t\t},\n\t\twatch: {\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\n\t\t\tparentData() {\n\t\t\t\tif (this.children.length) {\n\t\t\t\t\tthis.children.map(child => {\n\t\t\t\t\t\t// 判断子组件(u-radio)如果有init方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\n\t\t\t\t\t\ttypeof(child.init) === 'function' && child.init()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.children = []\n\t\t},\n\t\tmethods: {\n\t\t\t// 将其他的radio设置为未选中的状态\n\t\t\tunCheckedOther(childInstance) {\n\t\t\t\tthis.children.map(child => {\n\t\t\t\t\t// 所有子radio中，被操作组件实例的checked的值无需修改\n\t\t\t\t\tif (childInstance !== child) {\n\t\t\t\t\t\tchild.checked = false\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\tconst {\n\t\t\t\t\tname\n\t\t\t\t} = childInstance\n\t\t\t\t// 通过emit事件，设置父组件通过v-model双向绑定的值\n\t\t\t\tthis.$emit('input', name)\n\t\t\t\t// 发出事件\n\t\t\t\tthis.$emit('change', name)\n\t\t\t},\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-radio-group {\n\t\tflex: 1;\n\n\t\t&--row {\n\t\t\t@include flex;\n\t\t}\n\n\t\t&--column {\n\t\t\t@include flex(column);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-rate/props.js",
    "content": "export default {\n    props: {\n        // 用于v-model双向绑定选中的星星数量\n        value: {\n            type: [String, Number],\n            default: uni.$u.props.rate.value\n        },\n        // 要显示的星星数量\n        count: {\n            type: [String, Number],\n            default: uni.$u.props.rate.count\n        },\n        // 是否不可选中\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.rate.disabled\n        },\n        // 是否只读\n        readonly: {\n            type: Boolean,\n            default: uni.$u.props.rate.readonly\n        },\n        // 星星的大小，单位px\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.rate.size\n        },\n        // 未选中时的颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.rate.inactiveColor\n        },\n        // 选中的颜色\n        activeColor: {\n            type: String,\n            default: uni.$u.props.rate.activeColor\n        },\n        // 星星之间的间距，单位px\n        gutter: {\n            type: [String, Number],\n            default: uni.$u.props.rate.gutter\n        },\n        // 最少能选择的星星个数\n        minCount: {\n            type: [String, Number],\n            default: uni.$u.props.rate.minCount\n        },\n        // 是否允许半星\n        allowHalf: {\n            type: Boolean,\n            default: uni.$u.props.rate.allowHalf\n        },\n        // 选中时的图标(星星)\n        activeIcon: {\n            type: String,\n            default: uni.$u.props.rate.activeIcon\n        },\n        // 未选中时的图标(星星)\n        inactiveIcon: {\n            type: String,\n            default: uni.$u.props.rate.inactiveIcon\n        },\n        // 是否可以通过滑动手势选择评分\n        touchable: {\n            type: Boolean,\n            default: uni.$u.props.rate.touchable\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-rate/u-rate.vue",
    "content": "<template>\n    <view\n        class=\"u-rate\"\n        :id=\"elId\"\n        ref=\"u-rate\"\n        :style=\"[$u.addStyle(customStyle)]\"\n    >\n        <view\n            class=\"u-rate__content\"\n            @touchmove.stop=\"touchMove\"\n            @touchend.stop=\"touchEnd\"\n        >\n            <view\n                class=\"u-rate__content__item\"\n                v-for=\"(item, index) in Number(count)\"\n                :key=\"index\"\n                :class=\"[elClass]\"\n            >\n                <view\n                    class=\"u-rate__content__item__icon-wrap\"\n                    ref=\"u-rate__content__item__icon-wrap\"\n                    @tap.stop=\"clickHandler($event, index + 1)\"\n                >\n                    <u-icon\n                        :name=\"\n                            Math.floor(activeIndex) > index\n                                ? activeIcon\n                                : inactiveIcon\n                        \"\n                        :color=\"\n                            disabled\n                                ? '#c8c9cc'\n                                : Math.floor(activeIndex) > index\n                                ? activeColor\n                                : inactiveColor\n                        \"\n                        :custom-style=\"{\n                            'padding-left': $u.addUnit(gutter / 2),\n\t\t\t\t\t\t\t'padding-right': $u.addUnit(gutter / 2)\n                        }\"\n                        :size=\"size\"\n                    ></u-icon>\n                </view>\n                <view\n                    v-if=\"allowHalf\"\n                    @tap.stop=\"clickHandler($event, index + 1)\"\n                    class=\"u-rate__content__item__icon-wrap u-rate__content__item__icon-wrap--half\"\n                    :style=\"[{\n                        width: $u.addUnit(rateWidth / 2),\n                    }]\"\n                    ref=\"u-rate__content__item__icon-wrap\"\n                >\n                    <u-icon\n                        :name=\"\n                            Math.ceil(activeIndex) > index\n                                ? activeIcon\n                                : inactiveIcon\n                        \"\n                        :color=\"\n                            disabled\n                                ? '#c8c9cc'\n                                : Math.ceil(activeIndex) > index\n                                ? activeColor\n                                : inactiveColor\n                        \"\n                        :custom-style=\"{\n\t\t\t\t\t\t\t'padding-left': $u.addUnit(gutter / 2),\n\t\t\t\t\t\t\t'padding-right': $u.addUnit(gutter / 2)\n                        }\"\n                        :size=\"size\"\n                    ></u-icon>\n                </view>\n            </view>\n        </view>\n    </view>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t// #ifdef APP-NVUE\n\tconst dom = weex.requireModule(\"dom\");\n\t// #endif\n\t/**\n\t * rate 评分\n\t * @description 该组件一般用于满意度调查，星型评分的场景\n\t * @tutorial https://www.uviewui.com/components/rate.html\n\t * @property {String | Number}\tvalue\t\t\t用于v-model双向绑定选中的星星数量 (默认 1 )\n\t * @property {String | Number}\tcount\t\t\t最多可选的星星数量 （默认 5 ）\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁止用户操作 （默认 false ）\n\t * @property {Boolean}\t\t\treadonly\t\t是否只读 （默认 false ）\n\t * @property {String | Number}\tsize\t\t\t星星的大小，单位px （默认 18 ）\n\t * @property {String}\t\t\tinactiveColor\t未选中星星的颜色 （默认 '#b2b2b2' ）\n\t * @property {String}\t\t\tactiveColor\t\t选中的星星颜色 （默认 '#FA3534' ）\n\t * @property {String | Number}\tgutter\t\t\t星星之间的距离 （默认 4 ）\n\t * @property {String | Number}\tminCount\t\t最少选中星星的个数 （默认 1 ）\n\t * @property {Boolean}\t\t\tallowHalf\t\t是否允许半星选择 （默认 false ）\n\t * @property {String}\t\t\tactiveIcon\t\t选中时的图标名，只能为uView的内置图标 （默认 'star-fill' ）\n\t * @property {String}\t\t\tinactiveIcon\t未选中时的图标名，只能为uView的内置图标 （默认 'star' ）\n\t * @property {Boolean}\t\t\ttouchable\t\t是否可以通过滑动手势选择评分 （默认 'true' ）\n\t * @property {Object}\t\t\tcustomStyle\t\t组件的样式，对象形式\n\t * @event {Function} change 选中的星星发生变化时触发\n\t * @example <u-rate :count=\"count\" :value=\"2\"></u-rate>\n\t */\n\texport default {\n\t\tname: \"u-rate\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 生成一个唯一id，否则一个页面多个评分组件，会造成冲突\n\t\t\t\telId: uni.$u.guid(),\n\t\t\t\telClass: uni.$u.guid(),\n\t\t\t\trateBoxLeft: 0, // 评分盒子左边到屏幕左边的距离，用于滑动选择时计算距离\n\t\t\t\tactiveIndex: this.value,\n\t\t\t\trateWidth: 0, // 每个星星的宽度\n\t\t\t\t// 标识是否正在滑动，由于iOS事件上touch比click先触发，导致快速滑动结束后，接着触发click，导致事件混乱而出错\n\t\t\t\tmoving: false,\n\t\t\t};\n\t\t},\n\t\twatch: {\n\t\t\tvalue(val) {\n\t\t\t\tthis.activeIndex = val;\n\t\t\t},\n\t\t\tactiveIndex: 'emitEvent'\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tuni.$u.sleep().then(() => {\n\t\t\t\t\tthis.getRateItemRect();\n\t\t\t\t\tthis.getRateIconWrapRect();\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 获取评分组件盒子的布局信息\n\t\t\tasync getRateItemRect() {\n\t\t\t\tawait uni.$u.sleep();\n\t\t\t\t// uView封装的获取节点的方法，详见文档\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.$uGetRect(\"#\" + this.elId).then((res) => {\n\t\t\t\t\tthis.rateBoxLeft = res.left;\n\t\t\t\t});\n\t\t\t\t// #endif\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tdom.getComponentRect(this.$refs[\"u-rate\"], (res) => {\n\t\t\t\t\tthis.rateBoxLeft = res.size.left;\n\t\t\t\t});\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 获取单个星星的尺寸\n\t\t\tgetRateIconWrapRect() {\n\t\t\t\t// uView封装的获取节点的方法，详见文档\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.$uGetRect(\".\" + this.elClass).then((res) => {\n\t\t\t\t\tthis.rateWidth = res.width;\n\t\t\t\t});\n\t\t\t\t// #endif\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tdom.getComponentRect(\n\t\t\t\t\tthis.$refs[\"u-rate__content__item__icon-wrap\"][0],\n\t\t\t\t\t(res) => {\n\t\t\t\t\t\tthis.rateWidth = res.size.width;\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 手指滑动\n\t\t\ttouchMove(e) {\n\t\t\t\t// 如果禁止通过手动滑动选择，返回\n\t\t\t\tif (!this.touchable) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.preventEvent(e);\n\t\t\t\tconst x = e.changedTouches[0].pageX;\n\t\t\t\tthis.getActiveIndex(x);\n\t\t\t},\n\t\t\t// 停止滑动\n\t\t\ttouchEnd(e) {\n\t\t\t\t// 如果禁止通过手动滑动选择，返回\n\t\t\t\tif (!this.touchable) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.preventEvent(e);\n\t\t\t\tconst x = e.changedTouches[0].pageX;\n\t\t\t\tthis.getActiveIndex(x);\n\t\t\t},\n\t\t\t// 通过点击，直接选中\n\t\t\tclickHandler(e, index) {\n\t\t\t\t// ios上，moving状态取消事件触发\n\t\t\t\tif (uni.$u.os() === \"ios\" && this.moving) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.preventEvent(e);\n\t\t\t\tlet x = 0;\n\t\t\t\t// 点击时，在nvue上，无法获得点击的坐标，所以无法实现点击半星选择\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tx = e.changedTouches[0].pageX;\n\t\t\t\t// #endif\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// nvue下，无法通过点击获得坐标信息，这里通过元素的位置尺寸值模拟坐标\n\t\t\t\tx = index * this.rateWidth + this.rateBoxLeft;\n\t\t\t\t// #endif\n\t\t\t\tthis.getActiveIndex(x,true);\n\t\t\t},\n\t\t\t// 发出事件\n\t\t\temitEvent() {\n\t\t\t\t// 发出change事件\n\t\t\t\tthis.$emit(\"change\", this.activeIndex);\n\t\t\t\t// 同时修改双向绑定的value的值\n\t\t\t\tthis.$emit(\"input\", this.activeIndex);\n\t\t\t},\n\t\t\t// 获取当前激活的评分图标\n\t\t\tgetActiveIndex(x,isClick = false) {\n\t\t\t\tif (this.disabled || this.readonly) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// 判断当前操作的点的x坐标值，是否在允许的边界范围内\n\t\t\t\tconst allRateWidth = this.rateWidth * this.count + this.rateBoxLeft;\n\t\t\t\t// 如果小于第一个图标的左边界，设置为最小值，如果大于所有图标的宽度，则设置为最大值\n\t\t\t\tx = uni.$u.range(this.rateBoxLeft, allRateWidth, x) - this.rateBoxLeft\n\t\t\t\t// 滑动点相对于评分盒子左边的距离\n\t\t\t\tconst distance = x;\n\t\t\t\t// 滑动的距离，相当于多少颗星星\n\t\t\t\tlet index;\n\t\t\t\t// 判断是否允许半星\n\t\t\t\tif (this.allowHalf) {\n\t\t\t\t\tindex = Math.floor(distance / this.rateWidth);\n\t\t\t\t\t// 取余，判断小数的区间范围\n\t\t\t\t\tconst decimal = distance % this.rateWidth;\n\t\t\t\t\tif (decimal <= this.rateWidth / 2 && decimal > 0) {\n\t\t\t\t\t\tindex += 0.5;\n\t\t\t\t\t} else if (decimal > this.rateWidth / 2) {\n\t\t\t\t\t\tindex++;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tindex = Math.floor(distance / this.rateWidth);\n\t\t\t\t\t// 取余，判断小数的区间范围\n\t\t\t\t\tconst decimal = distance % this.rateWidth;\n\t\t\t\t\t// 非半星时，只有超过了图标的一半距离，才认为是选择了这颗星\n\t\t\t\t\tif (isClick){\n\t\t\t\t\t\tif (decimal > 0) index++;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (decimal > this.rateWidth / 2) index++;\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t\tthis.activeIndex = Math.min(index, this.count);\n\t\t\t\t// 对最少颗星星的限制\n\t\t\t\tif (this.activeIndex < this.minCount) {\n\t\t\t\t\tthis.activeIndex = this.minCount;\n\t\t\t\t}\n\n\t\t\t\t// 设置延时为了让click事件在touchmove之前触发\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.moving = true;\n\t\t\t\t}, 10);\n\t\t\t\t// 一定时间后，取消标识为移动中状态，是为了让click事件无效\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.moving = false;\n\t\t\t\t}, 10);\n\t\t\t},\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init();\n\t\t},\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n$u-rate-margin: 0 !default;\n$u-rate-padding: 0 !default;\n$u-rate-item-icon-wrap-half-top: 0 !default;\n$u-rate-item-icon-wrap-half-left: 0 !default;\n\n.u-rate {\n    @include flex;\n    align-items: center;\n    margin: $u-rate-margin;\n    padding: $u-rate-padding;\n    /* #ifndef APP-NVUE */\n    touch-action: none;\n    /* #endif */\n\n    &__content {\n        @include flex;\n\n\t\t&__item {\n\t\t    position: relative;\n\n\t\t    &__icon-wrap {\n\t\t        &--half {\n\t\t            position: absolute;\n\t\t            overflow: hidden;\n\t\t            top: $u-rate-item-icon-wrap-half-top;\n\t\t            left: $u-rate-item-icon-wrap-half-left;\n\t\t        }\n\t\t    }\n\t\t}\n    }\n}\n\n.u-icon {\n    /* #ifndef APP-NVUE */\n    box-sizing: border-box;\n    /* #endif */\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-read-more/props.js",
    "content": "export default {\n    props: {\n        // 默认的显示占位高度\n        showHeight: {\n            type: [String, Number],\n            default: uni.$u.props.readMore.showHeight\n        },\n        // 展开后是否显示\"收起\"按钮\n        toggle: {\n            type: Boolean,\n            default: uni.$u.props.readMore.toggle\n        },\n        // 关闭时的提示文字\n        closeText: {\n            type: String,\n            default: uni.$u.props.readMore.closeText\n        },\n        // 展开时的提示文字\n        openText: {\n            type: String,\n            default: uni.$u.props.readMore.openText\n        },\n        // 提示的文字颜色\n        color: {\n            type: String,\n            default: uni.$u.props.readMore.color\n        },\n        // 提示文字的大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.readMore.fontSize\n        },\n        // 是否显示阴影\n        // 此参数不能写在props/readMore.js中进行默认配置，因为使用了条件编译，在外部js中\n        // uni无法准确识别当前是否处于nvue还是非nvue下\n        shadowStyle: {\n            type: Object,\n            default: () => ({\n                // #ifndef APP-NVUE\n                backgroundImage: 'linear-gradient(-180deg, rgba(255, 255, 255, 0) 0%, #fff 80%)',\n                // #endif\n                // #ifdef APP-NVUE\n                // nvue上不支持设置复杂的backgroundImage属性\n                backgroundImage: 'linear-gradient(to top, #fff, rgba(255, 255, 255, 0.5))',\n                // #endif\n                paddingTop: '100px',\n                marginTop: '-100px'\n            })\n        },\n        // 段落首行缩进的字符个数\n        textIndent: {\n            type: String,\n            default: uni.$u.props.readMore.textIndent\n        },\n        // open和close事件时，将此参数返回在回调参数中\n        name: {\n            type: [String, Number],\n            default: uni.$u.props.readMore.name\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-read-more/u-read-more.vue",
    "content": "<template>\n\t<view class=\"u-read-more\">\n\t\t<view\n\t\t    class=\"u-read-more__content\"\n\t\t    :style=\"{\n\t\t\t\theight: isLongContent && status === 'close' ? $u.addUnit(showHeight) : $u.addUnit(contentHeight),\n\t\t\t\ttextIndent: textIndent\n\t\t\t}\"\n\t\t>\n\t\t\t<view\n\t\t\t    class=\"u-read-more__content__inner\"\n\t\t\t    ref=\"u-read-more__content__inner\"\n\t\t\t    :class=\"[elId]\"\n\t\t\t>\n\t\t\t\t<slot></slot>\n\t\t\t</view>\n\t\t</view>\n\t\t<view\n\t\t    class=\"u-read-more__toggle\"\n\t\t    :style=\"[innerShadowStyle]\"\n\t\t    v-if=\"isLongContent\"\n\t\t>\n\t\t\t<slot name=\"toggle\">\n\t\t\t\t<view\n\t\t\t\t    class=\"u-read-more__toggle__text\"\n\t\t\t\t    @tap=\"toggleReadMore\"\n\t\t\t\t>\n\t\t\t\t\t<u--text\n\t\t\t\t\t    :text=\"status === 'close' ? closeText : openText\"\n\t\t\t\t\t    :color=\"color\"\n\t\t\t\t\t    :size=\"fontSize\"\n\t\t\t\t\t    :lineHeight=\"fontSize\"\n\t\t\t\t\t    margin=\"0 5px 0 0\"\n\t\t\t\t\t></u--text>\n\t\t\t\t\t<view class=\"u-read-more__toggle__icon\">\n\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t    :color=\"color\"\n\t\t\t\t\t\t    :size=\"fontSize + 2\"\n\t\t\t\t\t\t    :name=\"status === 'close' ? 'arrow-down' : 'arrow-up'\"\n\t\t\t\t\t\t></u-icon>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</slot>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\timport props from './props.js';\n\t/**\n\t * readMore 阅读更多\n\t * @description 该组件一般用于内容较长，预先收起一部分，点击展开全部内容的场景。\n\t * @tutorial https://www.uviewui.com/components/readMore.html\n\t * @property {String | Number}\tshowHeight\t内容超出此高度才会显示展开全文按钮，单位px（默认 400 ）\n\t * @property {Boolean}\t\t\ttoggle\t\t展开后是否显示收起按钮（默认 false ）\n\t * @property {String}\t\t\tcloseText\t关闭时的提示文字（默认 '展开阅读全文' ）\n\t * @property {String}\t\t\topenText\t展开时的提示文字（默认 '收起' ）\n\t * @property {String}\t\t\tcolor\t\t提示文字的颜色（默认 '#2979ff' ）\n\t * @property {String | Number}\tfontSize\t提示文字的大小，单位px （默认 14 ）\n\t * @property {Object}\t\t\tshadowStyle\t显示阴影的样式\n\t * @property {String}\t\t\ttextIndent\t段落首行缩进的字符个数 （默认 '2em' ）\n\t * @property {String | Number}\tname\t\t用于在 open 和 close 事件中当作回调参数返回\n\t * @event {Function} open 内容被展开时触发\n\t * @event {Function} close 内容被收起时触发\n\t * @example <u-read-more><rich-text :nodes=\"content\"></rich-text></u-read-more>\n\t */\n\texport default {\n\t\tname: 'u-read-more',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisLongContent: false, // 是否需要隐藏一部分内容\n\t\t\t\tstatus: 'close', // 当前隐藏与显示的状态，close-收起状态，open-展开状态\n\t\t\t\telId: uni.$u.guid(), // 生成唯一class\n\t\t\t\tcontentHeight: 100, // 内容高度\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 展开后无需阴影，收起时才需要阴影样式\n\t\t\tinnerShadowStyle() {\n\t\t\t\tif (this.status === 'open') return {}\n\t\t\t\telse return this.shadowStyle\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tasync init() {\n\t\t\t\tthis.getContentHeight().then(height => {\n\t\t\t\t\tthis.contentHeight = height\n\t\t\t\t\t// 判断高度，如果真实内容高度大于占位高度，则显示收起与展开的控制按钮\n\t\t\t\t\tif (height > uni.$u.getPx(this.showHeight)) {\n\t\t\t\t\t\tthis.isLongContent = true\n\t\t\t\t\t\tthis.status = 'close'\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 获取内容的高度\n\t\t\tasync getContentHeight() {\n\t\t\t\t// 延时一定时间再获取节点\n\t\t\t\tawait uni.$u.sleep(30)\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\tthis.$uGetRect('.' + this.elId).then(res => {\n\t\t\t\t\t\tresolve(res.height)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\tconst ref = this.$refs['u-read-more__content__inner']\n\t\t\t\t\tdom.getComponentRect(ref, (res) => {\n\t\t\t\t\t\tresolve(res.size.height)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 展开或者收起\n\t\t\ttoggleReadMore() {\n\t\t\t\tthis.status = this.status === 'close' ? 'open' : 'close'\n\t\t\t\t// 如果toggle为false，隐藏\"收起\"部分的内容\n\t\t\t\tif (this.toggle == false) this.isLongContent = false\n\t\t\t\t// 发出打开或者收齐的事件\n\t\t\t\tthis.$emit(this.status, this.name)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n\n.u-read-more {\n\n\t&__content {\n\t\toverflow: hidden;\n\t\tcolor: $u-content-color;\n\t\tfont-size: 15px;\n\t\ttext-align: left;\n\t}\n\n\t&__toggle {\n\t\t@include flex;\n\t\tjustify-content: center;\n\n\t\t&__text {\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tmargin-top: 5px;\n\t\t}\n\t}\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-row/props.js",
    "content": "export default {\n    props: {\n        // 给col添加间距，左右边距各占一半\n        gutter: {\n            type: [String, Number],\n            default: uni.$u.props.row.gutter\n        },\n        // 水平排列方式，可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)\n        justify: {\n            type: String,\n            default: uni.$u.props.row.justify\n        },\n        // 垂直对齐方式，可选值为top、center、bottom\n        align: {\n            type: String,\n            default: uni.$u.props.row.align\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-row/u-row.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-row\"\n\t\tref=\"u-row\"\n\t    :style=\"[rowStyle]\"\n\t    @tap=\"clickHandler\"\n\t>\n\t\t<slot />\n\t</view>\n</template>\n\n<script>\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\timport props from './props.js';\n\t/**\n\t * Row 栅格系统中的行\n\t * @description 通过基础的 12 分栏，迅速简便地创建布局 \n\t * @tutorial https://www.uviewui.com/components/layout.html\n\t * @property {String | Number}\tgutter\t\t栅格间隔，左右各为此值的一半，单位px  (默认 0 )\n\t * @property {String}\t\t\tjustify\t\t水平排列方式(微信小程序暂不支持) 可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)  (默认 'start' )\n\t * @property {String}\t\t\talign\t\t垂直排列方式 (默认 'center' )\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * \n\t * @event {Function} click row被点击\n\t * @example <u-row justify=\"space-between\" customStyle=\"margin-bottom: 10px\"></u-row>\n\t */\n\texport default {\n\t\tname: \"u-row\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tuJustify() {\n\t\t\t\tif (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify\n\t\t\t\telse if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify\n\t\t\t\telse return this.justify\n\t\t\t},\n\t\t\tuAlignItem() {\n\t\t\t\tif (this.align == 'top') return 'flex-start'\n\t\t\t\tif (this.align == 'bottom') return 'flex-end'\n\t\t\t\telse return this.align\n\t\t\t},\n\t\t\trowStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\talignItems: this.uAlignItem,\n\t\t\t\t\tjustifyContent: this.uJustify\n\t\t\t\t}\n\t\t\t\t// 通过给u-row左右两边的负外边距，消除u-col在有gutter时，第一个和最后一个元素的左内边距和右内边距造成的影响\n\t\t\t\tif(this.gutter) {\n\t\t\t\t\tstyle.marginLeft = uni.$u.addUnit(-Number(this.gutter)/2)\n\t\t\t\t\tstyle.marginRight = uni.$u.addUnit(-Number(this.gutter)/2)\n\t\t\t\t}\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tclickHandler(e) {\n\t\t\t\tthis.$emit('click')\n\t\t\t},\n\t\t\tasync getComponentWidth() {\n\t\t\t\t// 延时一定时间，以确保节点渲染完成\n\t\t\t\tawait uni.$u.sleep()\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\t// uView封装的获取节点的方法，详见文档\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\tthis.$uGetRect('.u-row').then(res => {\n\t\t\t\t\t\tresolve(res.width)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\t// nvue的dom模块用于获取节点\n\t\t\t\t\tdom.getComponentRect(this.$refs['u-row'], (res) => {\n\t\t\t\t\t\tresolve(res.size.width)\n\t\t\t\t\t})\n\t\t\t\t\t// #endif\n\t\t\t\t})\n\t\t\t},\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t\n\t.u-row {\n\t\t@include flex;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-row-notice/props.js",
    "content": "export default {\n    props: {\n        // 显示的内容，字符串\n        text: {\n            type: String,\n            default: uni.$u.props.rowNotice.text\n        },\n        // 是否显示左侧的音量图标\n        icon: {\n            type: String,\n            default: uni.$u.props.rowNotice.icon\n        },\n        // 通告模式，link-显示右箭头，closable-显示右侧关闭图标\n        mode: {\n            type: String,\n            default: uni.$u.props.rowNotice.mode\n        },\n        // 文字颜色，各图标也会使用文字颜色\n        color: {\n            type: String,\n            default: uni.$u.props.rowNotice.color\n        },\n        // 背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.rowNotice.bgColor\n        },\n        // 字体大小，单位px\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.rowNotice.fontSize\n        },\n        // 水平滚动时的滚动速度，即每秒滚动多少px(rpx)，这有利于控制文字无论多少时，都能有一个恒定的速度\n        speed: {\n            type: [String, Number],\n            default: uni.$u.props.rowNotice.speed\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-row-notice/u-row-notice.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-notice\"\n\t\t@tap=\"clickHandler\"\n\t>\n\t\t<slot name=\"icon\">\n\t\t\t<view\n\t\t\t\tclass=\"u-notice__left-icon\"\n\t\t\t\tv-if=\"icon\"\n\t\t\t>\n\t\t\t\t<u-icon\n\t\t\t\t\t:name=\"icon\"\n\t\t\t\t\t:color=\"color\"\n\t\t\t\t\tsize=\"19\"\n\t\t\t\t></u-icon>\n\t\t\t</view>\n\t\t</slot>\n\t\t<view\n\t\t\tclass=\"u-notice__content\"\n\t\t\tref=\"u-notice__content\"\n\t\t>\n\t\t\t<view\n\t\t\t\tref=\"u-notice__content__text\"\n\t\t\t\tclass=\"u-notice__content__text\"\n\t\t\t\t:style=\"[animationStyle]\"\n\t\t\t>\n\t\t\t\t<text\n\t\t\t\t\tv-for=\"(item, index) in innerText\"\n\t\t\t\t\t:key=\"index\"\n\t\t\t\t\t:style=\"[textStyle]\"\n\t\t\t\t>{{item}}</text>\n\t\t\t</view>\n\t\t</view>\n\t\t<view\n\t\t\tclass=\"u-notice__right-icon\"\n\t\t\tv-if=\"['link', 'closable'].includes(mode)\"\n\t\t>\n\t\t\t<u-icon\n\t\t\t\tv-if=\"mode === 'link'\"\n\t\t\t\tname=\"arrow-right\"\n\t\t\t\t:size=\"17\"\n\t\t\t\t:color=\"color\"\n\t\t\t></u-icon>\n\t\t\t<u-icon\n\t\t\t\tv-if=\"mode === 'closable'\"\n\t\t\t\t@click=\"close\"\n\t\t\t\tname=\"close\"\n\t\t\t\t:size=\"16\"\n\t\t\t\t:color=\"color\"\n\t\t\t></u-icon>\n\t\t</view>\n\t</view>\n</template>\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst animation = uni.requireNativePlugin('animation')\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * RowNotice 滚动通知中的水平滚动模式\n\t * @description 水平滚动\n\t * @tutorial https://www.uviewui.com/components/noticeBar.html\n\t * @property {String | Number}\ttext\t\t\t显示的内容，字符串\n\t * @property {String}\t\t\ticon\t\t\t是否显示左侧的音量图标 (默认 'volume' )\n\t * @property {String}\t\t\tmode\t\t\t通告模式，link-显示右箭头，closable-显示右侧关闭图标\n\t * @property {String}\t\t\tcolor\t\t\t文字颜色，各图标也会使用文字颜色 (默认 '#f9ae3d' )\n\t * @property {String}\t\t\tbgColor\t\t\t背景颜色 (默认 ''#fdf6ec' )\n\t * @property {String | Number}\tfontSize\t\t字体大小，单位px (默认 14 )\n\t * @property {String | Number}\tspeed\t\t\t水平滚动时的滚动速度，即每秒滚动多少px(rpx)，这有利于控制文字无论多少时，都能有一个恒定的速度  (默认 80 )\n\t * \n\t * @event {Function} click 点击通告文字触发\n\t * @event {Function} close 点击右侧关闭图标触发\n\t * @example \n\t */\n\texport default {\n\t\tname: 'u-row-notice',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tanimationDuration: '0', // 动画执行时间\n\t\t\t\tanimationPlayState: 'paused', // 动画的开始和结束执行\n\t\t\t\t// nvue下，内容发生变化，导致滚动宽度也变化，需要标志为是否需要重新计算宽度\n\t\t\t\t// 不能在内容变化时直接重新计算，因为nvue的animation模块上一次的滚动不是刚好结束，会有影响\n\t\t\t\tnvueInit: true,\n\t\t\t\tshow: true\n\t\t\t};\n\t\t},\n\t\twatch: {\n\t\t\ttext: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(newValue, oldValue) {\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\tthis.nvueInit = true\n\t\t\t\t\t// #endif\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\tthis.vue()\n\t\t\t\t\t// #endif\n\t\t\t\t\t\n\t\t\t\t\tif(!uni.$u.test.string(newValue)) {\n\t\t\t\t\t\tuni.$u.error('noticebar组件direction为row时，要求text参数为字符串形式')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tfontSize() {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tthis.nvueInit = true\n\t\t\t\t// #endif\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.vue()\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\tspeed() {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tthis.nvueInit = true\n\t\t\t\t// #endif\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.vue()\n\t\t\t\t// #endif\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 文字内容的样式\n\t\t\ttextStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\tstyle.color = this.color\n\t\t\t\tstyle.fontSize = uni.$u.addUnit(this.fontSize)\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tanimationStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\tstyle.animationDuration = this.animationDuration\n\t\t\t\tstyle.animationPlayState = this.animationPlayState\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 内部对用户传入的数据进一步分割，放到多个text标签循环，否则如果用户传入的字符串很长（100个字符以上）\n\t\t\t// 放在一个text标签中进行滚动，在低端安卓机上，动画可能会出现抖动现象，需要分割到多个text中可解决此问题\n\t\t\tinnerText() {\n\t\t\t\tlet result = [],\n\t\t\t\t\t// 每组text标签的字符长度\n\t\t\t\t\tlen = 20\n\t\t\t\tconst textArr = this.text.split('')\n\t\t\t\tfor (let i = 0; i < textArr.length; i += len) {\n\t\t\t\t\t// 对拆分的后的text进行slice分割，得到的为数组再进行join拼接为字符串\n\t\t\t\t\tresult.push(textArr.slice(i, i + len).join(''))\n\t\t\t\t}\n\t\t\t\treturn result\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\t// #ifdef APP-PLUS\n\t\t\t// 在APP上(含nvue)，监听当前webview是否处于隐藏状态(进入下一页时即为hide状态)\n\t\t\t// 如果webivew隐藏了，为了节省性能的损耗，应停止动画的执行，同时也是为了保持进入下一页返回后，滚动位置保持不变\n\t\t\tvar pages = getCurrentPages()\n\t\t\tvar page = pages[pages.length - 1]\n\t\t\tvar currentWebview = page.$getAppWebview()\n\t\t\tcurrentWebview.addEventListener('hide', () => {\n\t\t\t\tthis.webviewHide = true\n\t\t\t})\n\t\t\tcurrentWebview.addEventListener('show', () => {\n\t\t\t\tthis.webviewHide = false\n\t\t\t})\n\t\t\t// #endif\n\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tthis.nvue()\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.vue()\n\t\t\t\t// #endif\n\t\t\t\t\n\t\t\t\tif(!uni.$u.test.string(this.text)) {\n\t\t\t\t\tuni.$u.error('noticebar组件direction为row时，要求text参数为字符串形式')\n\t\t\t\t}\n\t\t\t},\n\t\t\t// vue版处理\n\t\t\tasync vue() {\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tlet boxWidth = 0,\n\t\t\t\t\ttextWidth = 0\n\t\t\t\t// 进行一定的延时\n\t\t\t\tawait uni.$u.sleep()\n\t\t\t\t// 查询盒子和文字的宽度\n\t\t\t\ttextWidth = (await this.$uGetRect('.u-notice__content__text')).width\n\t\t\t\tboxWidth = (await this.$uGetRect('.u-notice__content')).width\n\t\t\t\t// 根据t=s/v(时间=路程/速度)，这里为何不需要加上#u-notice-box的宽度，因为中设置了.u-notice-content样式中设置了padding-left: 100%\n\t\t\t\t// 恰巧计算出来的结果中已经包含了#u-notice-box的宽度\n\t\t\t\tthis.animationDuration = `${textWidth / uni.$u.getPx(this.speed)}s`\n\t\t\t\t// 这里必须这样开始动画，否则在APP上动画速度不会改变\n\t\t\t\tthis.animationPlayState = 'paused'\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.animationPlayState = 'running'\n\t\t\t\t}, 10)\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// nvue版处理\n\t\t\tasync nvue() {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tthis.nvueInit = false\n\t\t\t\tlet boxWidth = 0,\n\t\t\t\t\ttextWidth = 0\n\t\t\t\t// 进行一定的延时\n\t\t\t\tawait uni.$u.sleep()\n\t\t\t\t// 查询盒子和文字的宽度\n\t\t\t\ttextWidth = (await this.getNvueRect('u-notice__content__text')).width\n\t\t\t\tboxWidth = (await this.getNvueRect('u-notice__content')).width\n\t\t\t\t// 将文字移动到盒子的右边沿，之所以需要这么做，是因为nvue不支持100%单位，否则可以通过css设置\n\t\t\t\tanimation.transition(this.$refs['u-notice__content__text'], {\n\t\t\t\t\tstyles: {\n\t\t\t\t\t\ttransform: `translateX(${boxWidth}px)`\n\t\t\t\t\t},\n\t\t\t\t}, () => {\n\t\t\t\t\t// 如果非禁止动画，则开始滚动\n\t\t\t\t\t!this.stopAnimation && this.loopAnimation(textWidth, boxWidth)\n\t\t\t\t});\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\tloopAnimation(textWidth, boxWidth) {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tanimation.transition(this.$refs['u-notice__content__text'], {\n\t\t\t\t\tstyles: {\n\t\t\t\t\t\t// 目标移动终点为-textWidth，也即当文字的最右边贴到盒子的左边框的位置\n\t\t\t\t\t\ttransform: `translateX(-${textWidth}px)`\n\t\t\t\t\t},\n\t\t\t\t\t// 滚动时间的计算为，时间 = 路程(boxWidth + textWidth) / 速度，最后转为毫秒\n\t\t\t\t\tduration: (boxWidth + textWidth) / uni.$u.getPx(this.speed) * 1000,\n\t\t\t\t\tdelay: 10\n\t\t\t\t}, () => {\n\t\t\t\t\tanimation.transition(this.$refs['u-notice__content__text'], {\n\t\t\t\t\t\tstyles: {\n\t\t\t\t\t\t\t// 重新将文字移动到盒子的右边沿\n\t\t\t\t\t\t\ttransform: `translateX(${this.stopAnimation ? 0 : boxWidth}px)`\n\t\t\t\t\t\t},\n\t\t\t\t\t}, () => {\n\t\t\t\t\t\t// 如果非禁止动画，则继续下一轮滚动\n\t\t\t\t\t\tif (!this.stopAnimation) {\n\t\t\t\t\t\t\t// 判断是否需要初始化计算尺寸\n\t\t\t\t\t\t\tif (this.nvueInit) {\n\t\t\t\t\t\t\t\tthis.nvue()\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.loopAnimation(textWidth, boxWidth)\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// #endif\n\t\t\t},\n\t\t\tgetNvueRect(el) {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// 返回一个promise\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tdom.getComponentRect(this.$refs[el], (res) => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 点击通告栏\n\t\t\tclickHandler(index) {\n\t\t\t\tthis.$emit('click')\n\t\t\t},\n\t\t\t// 点击右侧按钮，需要判断点击的是关闭图标还是箭头图标\n\t\t\tclose() {\n\t\t\t\tthis.$emit('close')\n\t\t\t}\n\t\t},\n\t\t// #ifdef APP-NVUE\n\t\tbeforeDestroy() {\n\t\t\tthis.stopAnimation = true\n\t\t},\n\t\t// #endif\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-notice {\n\t\t@include flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\n\t\t&__left-icon {\n\t\t\talign-items: center;\n\t\t\tmargin-right: 5px;\n\t\t}\n\n\t\t&__right-icon {\n\t\t\tmargin-left: 5px;\n\t\t\talign-items: center;\n\t\t}\n\n\t\t&__content {\n\t\t\ttext-align: right;\n\t\t\tflex: 1;\n\t\t\t@include flex;\n\t\t\tflex-wrap: nowrap;\n\t\t\toverflow: hidden;\n\n\t\t\t&__text {\n\t\t\t\tfont-size: 14px;\n\t\t\t\tcolor: $u-warning;\n\t\t\t\t/* #ifndef APP-NVUE */\n\t\t\t\t// 这一句很重要，为了能让滚动左右连接起来\n\t\t\t\tpadding-left: 100%;\n\t\t\t\tword-break: keep-all;\n\t\t\t\twhite-space: nowrap;\n\t\t\t\tanimation: u-loop-animation 10s linear infinite both;\n\t\t\t\t/* #endif */\n\t\t\t\t@include flex(row);\n\t\t\t}\n\t\t}\n\n\t}\n\n\t@keyframes u-loop-animation {\n\t\t0% {\n\t\t\ttransform: translate3d(0, 0, 0);\n\t\t}\n\n\t\t100% {\n\t\t\ttransform: translate3d(-100%, 0, 0);\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-safe-bottom/props.js",
    "content": "export default {\n    props: {\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-safe-bottom/u-safe-bottom.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-safe-bottom\"\n\t\t:style=\"[style]\"\n\t\t:class=\"[!isNvue && 'u-safe-area-inset-bottom']\"\n\t>\n\t</view>\n</template>\n\n<script>\n\timport props from \"./props.js\";\n\t/**\n\t * SafeBottom 底部安全区\n\t * @description 这个适配，主要是针对IPhone X等一些底部带指示条的机型，指示条的操作区域与页面底部存在重合，容易导致用户误操作，因此我们需要针对这些机型进行底部安全区适配。\n\t * @tutorial https://www.uviewui.com/components/safeAreaInset.html\n\t * @property {type}\t\tprop_name\n\t * @property {Object}\tcustomStyle\t定义需要用到的外部样式\n\t *\n\t * @event {Function()}\n\t * @example <u-status-bar></u-status-bar>\n\t */\n\texport default {\n\t\tname: \"u-safe-bottom\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tsafeAreaBottomHeight: 0,\n\t\t\t\tisNvue: false,\n\t\t\t};\n\t\t},\n\t\tcomputed: {\n\t\t\tstyle() {\n\t\t\t\tconst style = {};\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// nvue下，高度使用js计算填充\n\t\t\t\tstyle.height = uni.$u.addUnit(uni.$u.sys().safeAreaInsets.bottom, 'px');\n\t\t\t\t// #endif\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));\n\t\t\t},\n\t\t},\n\t\tmounted() {\n\t\t\t// #ifdef APP-NVUE\n\t\t\t// 标识为是否nvue\n\t\t\tthis.isNvue = true;\n\t\t\t// #endif\n\t\t},\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t.u-safe-bottom {\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: 100%;\n\t\t/* #endif */\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-scroll-list/nvue.js",
    "content": "// 引入bindingx，此库类似于微信小程序wxs，目的是让js运行在视图层，减少视图层和逻辑层的通信折损\nconst BindingX = uni.requireNativePlugin('bindingx')\n\nexport default {\n    methods: {\n        // 此处不写注释，请自行体会\n        nvueScrollHandler(e) {\n            const anchor = this.$refs['u-scroll-list__scroll-view'].ref\n            const element = this.$refs['u-scroll-list__indicator__line__bar'].ref\n            const scrollLeft = e.contentOffset.x\n            const contentSize = e.contentSize.width\n            const { scrollWidth } = this\n            const barAllMoveWidth = this.indicatorWidth - this.indicatorBarWidth\n            // 在安卓和iOS上，需要除的倍数不一样，iOS需要除以2\n            const actionNum = uni.$u.os() === 'ios' ? 2 : 1\n            const expression = `(x / ${actionNum}) / ${contentSize - scrollWidth} * ${barAllMoveWidth}`\n            BindingX.bind({\n                anchor,\n                eventType: 'scroll',\n                props: [{\n                    element,\n                    property: 'transform.translateX',\n                    expression\n                }]\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-scroll-list/other.js",
    "content": ""
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-scroll-list/props.js",
    "content": "export default {\n    props: {\n        // 指示器的整体宽度\n        indicatorWidth: {\n            type: [String, Number],\n            default: uni.$u.props.scrollList.indicatorWidth\n        },\n        // 滑块的宽度\n        indicatorBarWidth: {\n            type: [String, Number],\n            default: uni.$u.props.scrollList.indicatorBarWidth\n        },\n        // 是否显示面板指示器\n        indicator: {\n            type: Boolean,\n            default: uni.$u.props.scrollList.indicator\n        },\n        // 指示器非激活颜色\n        indicatorColor: {\n            type: String,\n            default: uni.$u.props.scrollList.indicatorColor\n        },\n        // 指示器的激活颜色\n        indicatorActiveColor: {\n            type: String,\n            default: uni.$u.props.scrollList.indicatorActiveColor\n        },\n        // 指示器样式，可通过bottom，left，right进行定位\n        indicatorStyle: {\n            type: [String, Object],\n            default: uni.$u.props.scrollList.indicatorStyle\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-scroll-list/scrollWxs.wxs",
    "content": "function scroll(event, ownerInstance) {\n\t// detail中含有scroll-view的信息，比如scroll-view的实际宽度，当前时间点scroll-view的移动距离等\n\tvar detail = event.detail\n\tvar scrollWidth = detail.scrollWidth\n\tvar scrollLeft = detail.scrollLeft\n\t// 获取当前组件的dataset，说白了就是祸国殃民的腾xun搞出来的垃ji\n\tvar dataset = event.currentTarget.dataset\n\t// 此为scroll-view外部包裹元素的宽度\n\t// 某些HX版本(3.1.18)，发现view元素中大写的data-scrollWidth，在wxs中，变成了全部小写，所以这里需要特别处理\n\tvar scrollComponentWidth = dataset.scrollWidth || dataset.scrollwidth || 0\n\t// 指示器和滑块的宽度\n\tvar indicatorWidth = dataset.indicatorWidth || dataset.indicatorwidth || 0\n\tvar barWidth = dataset.barWidth || dataset.barwidth || 0\n\t// 此处的计算理由为：scroll-view的滚动距离与目标滚动距离(scroll-view的实际宽度减去包裹元素的宽度)之比，等于滑块当前移动距离与总需\n\t// 滑动距离(指示器的总宽度减去滑块宽度)的比值\n\tvar x = scrollLeft / (scrollWidth - scrollComponentWidth) * (indicatorWidth - barWidth)\n\tsetBarStyle(ownerInstance, x)\n}\n\n// 由于webview的无能，无法保证scroll-view在滑动过程中，一直触发scroll事件，会导致\n// 无法监听到某些滚动值，当在首尾临界值无法监听到时，这是致命的，因为错失这些值会导致滑块无法回到起点和终点\n// 所以这里需要对临界值做监听并处理\nfunction scrolltolower(event, ownerInstance) {\n\townerInstance.callMethod('scrollEvent', 'right')\n\t// 获取当前组件的dataset\n\tvar dataset = event.currentTarget.dataset\n\t// 指示器和滑块的宽度\n\tvar indicatorWidth = dataset.indicatorWidth || dataset.indicatorwidth || 0\n\tvar barWidth = dataset.barWidth || dataset.barwidth || 0\n\t// scroll-view滚动到右边终点时，将滑块也设置为到右边的终点，它所需移动的距离为：指示器宽度 - 滑块宽度\n\tsetBarStyle(ownerInstance, indicatorWidth - barWidth)\n}\n\nfunction scrolltoupper(event, ownerInstance) {\n\townerInstance.callMethod('scrollEvent', 'left')\n\t// 滚动到左边时，将滑块设置为0的偏移距离，回到起点\n\tsetBarStyle(ownerInstance, 0)\n}\n\nfunction setBarStyle(ownerInstance, x) {\n\townerInstance.selectComponent('.u-scroll-list__indicator__line__bar') && ownerInstance.selectComponent('.u-scroll-list__indicator__line__bar').setStyle({\n\t\ttransform: 'translateX(' + x + 'px)'\n\t})\n}\n\nmodule.exports = {\n\tscroll: scroll,\n\tscrolltolower: scrolltolower,\n\tscrolltoupper: scrolltoupper\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-scroll-list/u-scroll-list.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-scroll-list\"\n\t\tref=\"u-scroll-list\"\n\t>\n\t\t<!-- #ifdef APP-NVUE -->\n\t\t<!-- nvue使用bindingX实现，以得到更好的性能 -->\n\t\t<scroller\n\t\t\tclass=\"u-scroll-list__scroll-view\"\n\t\t\tref=\"u-scroll-list__scroll-view\"\n\t\t\tscroll-direction=\"horizontal\"\n\t\t\t:show-scrollbar=\"false\"\n\t\t\t:offset-accuracy=\"1\"\n\t\t\t@scroll=\"nvueScrollHandler\"\n\t\t>\n\t\t\t<view class=\"u-scroll-list__scroll-view__content\">\n\t\t\t\t<slot />\n\t\t\t</view>\n\t\t</scroller>\n\t\t<!-- #endif -->\n\t\t<!-- #ifndef APP-NVUE -->\n\t\t<!-- #ifdef MP-WEIXIN || APP-VUE || H5 || MP-QQ -->\n\t\t<!-- 以上平台，支持wxs -->\n\t\t<scroll-view\n\t\t\tclass=\"u-scroll-list__scroll-view\"\n\t\t\tscroll-x\n\t\t\t@scroll=\"wxs.scroll\"\n\t\t\t@scrolltoupper=\"wxs.scrolltoupper\"\n\t\t\t@scrolltolower=\"wxs.scrolltolower\"\n\t\t\t:data-scrollWidth=\"scrollWidth\"\n\t\t\t:data-barWidth=\"$u.getPx(indicatorBarWidth)\"\n\t\t\t:data-indicatorWidth=\"$u.getPx(indicatorWidth)\"\n\t\t\t:show-scrollbar=\"false\"\n\t\t\t:upper-threshold=\"0\"\n\t\t\t:lower-threshold=\"0\"\n\t\t>\n\t\t\t<!-- #endif -->\n\t\t\t<!-- #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ -->\n\t\t\t<!-- 非以上平台，只能使用普通js实现 -->\n\t\t\t<scroll-view\n\t\t\t\tclass=\"u-scroll-list__scroll-view\"\n\t\t\t\tscroll-x\n\t\t\t\t@scroll=\"scrollHandler\"\n\t\t\t\t@scrolltoupper=\"scrolltoupperHandler\"\n\t\t\t\t@scrolltolower=\"scrolltolowerHandler\"\n\t\t\t\t:show-scrollbar=\"false\"\n\t\t\t\t:upper-threshold=\"0\"\n\t\t\t\t:lower-threshold=\"0\"\n\t\t\t>\n\t\t\t\t<!-- #endif -->\n\t\t\t\t<view class=\"u-scroll-list__scroll-view__content\">\n\t\t\t\t\t<slot />\n\t\t\t\t</view>\n\t\t\t</scroll-view>\n\t\t\t<!-- #endif -->\n\t\t\t<view\n\t\t\t\tclass=\"u-scroll-list__indicator\"\n\t\t\t\tv-if=\"indicator\"\n\t\t\t\t:style=\"[$u.addStyle(indicatorStyle)]\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-scroll-list__indicator__line\"\n\t\t\t\t\t:style=\"[lineStyle]\"\n\t\t\t\t>\n\t\t\t\t\t<view\n\t\t\t\t\t\tclass=\"u-scroll-list__indicator__line__bar\"\n\t\t\t\t\t\t:style=\"[barStyle]\"\n\t\t\t\t\t\tref=\"u-scroll-list__indicator__line__bar\"\n\t\t\t\t\t></view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t</view>\n</template>\n\n<script\n\tsrc=\"./scrollWxs.wxs\"\n\tmodule=\"wxs\"\n\tlang=\"wxs\"\n></script>\n\n<script>\n/**\n * scrollList 横向滚动列表\n * @description 该组件一般用于同时展示多个商品、分类的场景，也可以完成左右滑动的列表。\n * @tutorial https://www.uviewui.com/components/scrollList.html\n * @property {String | Number}\tindicatorWidth\t\t\t指示器的整体宽度 (默认 50 )\n * @property {String | Number}\tindicatorBarWidth\t\t滑块的宽度 (默认 20 )\n * @property {Boolean}\t\t\tindicator\t\t\t\t是否显示面板指示器 (默认 true )\n * @property {String}\t\t\tindicatorColor\t\t\t指示器非激活颜色 (默认 '#f2f2f2' )\n * @property {String}\t\t\tindicatorActiveColor\t指示器的激活颜色 (默认 '#3c9cff' )\n * @property {String | Object}\tindicatorStyle\t\t\t指示器样式，可通过bottom，left，right进行定位\n * @event {Function} left\t滑动到左边时触发\n * @event {Function} right\t滑动到右边时触发\n * @example\n */\n// #ifdef APP-NVUE\nconst dom = uni.requireNativePlugin('dom')\nimport nvueMixin from \"./nvue.js\"\n// #endif\nimport props from './props.js';\nexport default {\n\tname: 'u-scroll-list',\n\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t// #ifdef APP-NVUE\n\tmixins: [uni.$u.mpMixin, uni.$u.mixin, nvueMixin, props],\n\t// #endif\n\tdata() {\n\t\treturn {\n\t\t\tscrollInfo: {\n\t\t\t\tscrollLeft: 0,\n\t\t\t\tscrollWidth: 0\n\t\t\t},\n\t\t\tscrollWidth: 0\n\t\t}\n\t},\n\tcomputed: {\n\t\t// 指示器为线型的样式\n\t\tbarStyle() {\n\t\t\tconst style = {}\n\t\t\t// #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ\n\t\t\t// 此为普通js方案，只有在非nvue和不支持wxs方案的端才使用、\n\t\t\t// 此处的计算理由为：scroll-view的滚动距离与目标滚动距离(scroll-view的实际宽度减去包裹元素的宽度)之比，等于滑块当前移动距离与总需\n\t\t\t// 滑动距离(指示器的总宽度减去滑块宽度)的比值\n\t\t\tconst scrollLeft = this.scrollInfo.scrollLeft,\n\t\t\t\tscrollWidth = this.scrollInfo.scrollWidth,\n\t\t\t\tbarAllMoveWidth = this.indicatorWidth - this.indicatorBarWidth\n\t\t\tconst x = scrollLeft / (scrollWidth - this.scrollWidth) * barAllMoveWidth\n\t\t\tstyle.transform = `translateX(${ x }px)`\n\t\t\t// #endif\n\t\t\t// 设置滑块的宽度和背景色，是每个平台都需要的\n\t\t\tstyle.width = uni.$u.addUnit(this.indicatorBarWidth)\n\t\t\tstyle.backgroundColor = this.indicatorActiveColor\n\t\t\treturn style\n\t\t},\n\t\tlineStyle() {\n\t\t\tconst style = {}\n\t\t\t// 指示器整体的样式，需要设置其宽度和背景色\n\t\t\tstyle.width = uni.$u.addUnit(this.indicatorWidth)\n\t\t\tstyle.backgroundColor = this.indicatorColor\n\t\t\treturn style\n\t\t}\n\t},\n\tmounted() {\n\t\tthis.init()\n\t},\n\tmethods: {\n\t\tinit() {\n\t\t\tthis.getComponentWidth()\n\t\t},\n\t\t// #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ\n\t\t// scroll-view触发滚动事件\n\t\tscrollHandler(e) {\n\t\t\tthis.scrollInfo = e.detail\n\t\t},\n\t\tscrolltoupperHandler() {\n\t\t\tthis.scrollEvent('left')\n\t\t\tthis.scrollInfo.scrollLeft = 0\n\t\t},\n\t\tscrolltolowerHandler() {\n\t\t\tthis.scrollEvent('right')\n\t\t\t// 在普通js方案中，滚动到右边时，通过设置this.scrollInfo，模拟出滚动到右边的情况\n\t\t\t// 因为上方是用过computed计算的，设置后，会自动调整滑块的位置\n\t\t\tthis.scrollInfo.scrollLeft = uni.$u.getPx(this.indicatorWidth) - uni.$u.getPx(this.indicatorBarWidth)\n\t\t},\n\t\t// #endif\n\t\t//\n\t\tscrollEvent(status) {\n\t\t\tthis.$emit(status)\n\t\t},\n\t\t// 获取组件的宽度\n\t\tasync getComponentWidth() {\n\t\t\t// 延时一定时间，以获取dom尺寸\n\t\t\tawait uni.$u.sleep(30)\n\t\t\t// #ifndef APP-NVUE\n\t\t\tthis.$uGetRect('.u-scroll-list').then(size => {\n\t\t\t\tthis.scrollWidth = size.width\n\t\t\t})\n\t\t\t// #endif\n\n\t\t\t// #ifdef APP-NVUE\n\t\t\tconst ref = this.$refs['u-scroll-list']\n\t\t\tref && dom.getComponentRect(ref, (res) => {\n\t\t\t\tthis.scrollWidth = res.size.width\n\t\t\t})\n\t\t\t// #endif\n\t\t},\n\t}\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n\n.u-scroll-list {\n\tpadding-bottom: 10px;\n\n\t&__scroll-view {\n\t\t@include flex;\n\n\t\t&__content {\n\t\t\t@include flex;\n\t\t}\n\t}\n\n\t&__indicator {\n\t\t@include flex;\n\t\tjustify-content: center;\n\t\tmargin-top: 15px;\n\n\t\t&__line {\n\t\t\twidth: 60px;\n\t\t\theight: 4px;\n\t\t\tborder-radius: 100px;\n\t\t\toverflow: hidden;\n\n\t\t\t&__bar {\n\t\t\t\twidth: 20px;\n\t\t\t\theight: 4px;\n\t\t\t\tborder-radius: 100px;\n\t\t\t}\n\t\t}\n\t}\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-search/props.js",
    "content": "export default {\n    props: {\n        // 搜索框形状，round-圆形，square-方形\n        shape: {\n            type: String,\n            default: uni.$u.props.search.shape\n        },\n        // 搜索框背景色，默认值#f2f2f2\n        bgColor: {\n            type: String,\n            default: uni.$u.props.search.bgColor\n        },\n        // 占位提示文字\n        placeholder: {\n            type: String,\n            default: uni.$u.props.search.placeholder\n        },\n        // 是否启用清除控件\n        clearabled: {\n            type: Boolean,\n            default: uni.$u.props.search.clearabled\n        },\n        // 是否自动聚焦\n        focus: {\n            type: Boolean,\n            default: uni.$u.props.search.focus\n        },\n        // 是否在搜索框右侧显示取消按钮\n        showAction: {\n            type: Boolean,\n            default: uni.$u.props.search.showAction\n        },\n        // 右边控件的样式\n        actionStyle: {\n            type: Object,\n            default: uni.$u.props.search.actionStyle\n        },\n        // 取消按钮文字\n        actionText: {\n            type: String,\n            default: uni.$u.props.search.actionText\n        },\n        // 输入框内容对齐方式，可选值为 left|center|right\n        inputAlign: {\n            type: String,\n            default: uni.$u.props.search.inputAlign\n        },\n        // input输入框的样式，可以定义文字颜色，大小等，对象形式\n        inputStyle: {\n            type: Object,\n            default: uni.$u.props.search.inputStyle\n        },\n        // 是否启用输入框\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.search.disabled\n        },\n        // 边框颜色\n        borderColor: {\n            type: String,\n            default: uni.$u.props.search.borderColor\n        },\n        // 搜索图标的颜色，默认同输入框字体颜色\n        searchIconColor: {\n            type: String,\n            default: uni.$u.props.search.searchIconColor\n        },\n        // 输入框字体颜色\n        color: {\n            type: String,\n            default: uni.$u.props.search.color\n        },\n        // placeholder的颜色\n        placeholderColor: {\n            type: String,\n            default: uni.$u.props.search.placeholderColor\n        },\n        // 左边输入框的图标，可以为uView图标名称或图片路径\n        searchIcon: {\n            type: String,\n            default: uni.$u.props.search.searchIcon\n        },\n        searchIconSize: {\n            type: [Number, String],\n            default: uni.$u.props.search.searchIconSize\n        },\n        // 组件与其他上下左右元素之间的距离，带单位的字符串形式，如\"30px\"、\"30px 20px\"等写法\n        margin: {\n            type: String,\n            default: uni.$u.props.search.margin\n        },\n        // 开启showAction时，是否在input获取焦点时才显示\n        animation: {\n            type: Boolean,\n            default: uni.$u.props.search.animation\n        },\n        // 输入框的初始化内容\n        value: {\n            type: String,\n            default: uni.$u.props.search.value\n        },\n        // 输入框最大能输入的长度，-1为不限制长度(来自uniapp文档)\n        maxlength: {\n            type: [String, Number],\n            default: uni.$u.props.search.maxlength\n        },\n        // 搜索框高度，单位px\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.search.height\n        },\n        // 搜索框左侧文本\n        label: {\n            type: [String, Number, null],\n            default: uni.$u.props.search.label\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-search/u-search.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-search\"\n\t    @tap=\"clickHandler\"\n\t    :style=\"[{\n\t\t\tmargin: margin,\n\t\t}, $u.addStyle(customStyle)]\"\n\t>\n\t\t<view\n\t\t    class=\"u-search__content\"\n\t\t    :style=\"{\n\t\t\t\tbackgroundColor: bgColor,\n\t\t\t\tborderRadius: shape == 'round' ? '100px' : '4px',\n\t\t\t\tborderColor: borderColor,\n\t\t\t}\"\n\t\t>\n\t\t\t<template v-if=\"$slots.label || label !== null\">\n\t\t\t\t<slot name=\"label\">\n\t\t\t\t\t<text class=\"u-search__content__label\">{{ label }}</text>\n\t\t\t\t</slot>\n\t\t\t</template>\n\t\t\t<view class=\"u-search__content__icon\">\n\t\t\t\t<u-icon\n\t\t\t\t\t@tap=\"clickIcon\"\n\t\t\t\t    :size=\"searchIconSize\"\n\t\t\t\t    :name=\"searchIcon\"\n\t\t\t\t    :color=\"searchIconColor ? searchIconColor : color\"\n\t\t\t\t></u-icon>\n\t\t\t</view>\n\t\t\t<input\n\t\t\t    confirm-type=\"search\"\n\t\t\t    @blur=\"blur\"\n\t\t\t    :value=\"value\"\n\t\t\t    @confirm=\"search\"\n\t\t\t    @input=\"inputChange\"\n\t\t\t    :disabled=\"disabled\"\n\t\t\t    @focus=\"getFocus\"\n\t\t\t    :focus=\"focus\"\n\t\t\t    :maxlength=\"maxlength\"\n\t\t\t    placeholder-class=\"u-search__content__input--placeholder\"\n\t\t\t    :placeholder=\"placeholder\"\n\t\t\t    :placeholder-style=\"`color: ${placeholderColor}`\"\n\t\t\t    class=\"u-search__content__input\"\n\t\t\t    type=\"text\"\n\t\t\t    :style=\"[{\n\t\t\t\t\ttextAlign: inputAlign,\n\t\t\t\t\tcolor: color,\n\t\t\t\t\tbackgroundColor: bgColor,\n\t\t\t\t\theight: $u.addUnit(height)\n\t\t\t\t}, inputStyle]\"\n\t\t\t/>\n\t\t\t<view\n\t\t\t    class=\"u-search__content__icon u-search__content__close\"\n\t\t\t    v-if=\"keyword && clearabled && focused\"\n\t\t\t    @tap=\"clear\"\n\t\t\t>\n\t\t\t\t<u-icon\n\t\t\t\t    name=\"close\"\n\t\t\t\t    size=\"11\"\n\t\t\t\t    color=\"#ffffff\"\n\t\t\t\t\tcustomStyle=\"line-height: 12px\"\n\t\t\t\t></u-icon>\n\t\t\t</view>\n\t\t</view>\n\t\t<text\n\t\t    :style=\"[actionStyle]\"\n\t\t    class=\"u-search__action\"\n\t\t    :class=\"[(showActionBtn || show) && 'u-search__action--active']\"\n\t\t    @tap.stop.prevent=\"custom\"\n\t\t>{{ actionText }}</text>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\n\t/**\n\t * search 搜索框\n\t * @description 搜索组件，集成了常见搜索框所需功能，用户可以一键引入，开箱即用。\n\t * @tutorial https://www.uviewui.com/components/search.html\n\t * @property {String}\t\t\tshape\t\t\t\t搜索框形状，round-圆形，square-方形（默认 'round' ）\n\t * @property {String}\t\t\tbgColor\t\t\t\t搜索框背景颜色（默认 '#f2f2f2' ）\n\t * @property {String}\t\t\tplaceholder\t\t\t占位文字内容（默认 '请输入关键字' ）\n\t * @property {Boolean}\t\t\tclearabled\t\t\t是否启用清除控件（默认 true ）\n\t * @property {Boolean}\t\t\tfocus\t\t\t\t是否自动获得焦点（默认 false ）\n\t * @property {Boolean}\t\t\tshowAction\t\t\t是否显示右侧控件（默认 true ）\n\t * @property {Object}\t\t\tactionStyle\t\t\t右侧控件的样式，对象形式\n\t * @property {String}\t\t\tactionText\t\t\t右侧控件文字（默认 '搜索' ）\n\t * @property {String}\t\t\tinputAlign\t\t\t输入框内容水平对齐方式 （默认 'left' ）\n\t * @property {Object}\t\t\tinputStyle\t\t\t自定义输入框样式，对象形式\n\t * @property {Boolean}\t\t\tdisabled\t\t\t是否启用输入框（默认 false ）\n\t * @property {String}\t\t\tborderColor\t\t\t边框颜色，配置了颜色，才会有边框 (默认 'transparent' )\n\t * @property {String}\t\t\tsearchIconColor\t\t搜索图标的颜色，默认同输入框字体颜色 (默认 '#909399' )\n\t * @property {Number | String}\tsearchIconSize 搜索图标的字体，默认22\n\t * @property {String}\t\t\tcolor\t\t\t\t输入框字体颜色（默认 '#606266' ）\n\t * @property {String}\t\t\tplaceholderColor\tplaceholder的颜色（默认 '#909399' ）\n\t * @property {String}\t\t\tsearchIcon\t\t\t输入框左边的图标，可以为uView图标名称或图片路径  (默认 'search' )\n\t * @property {String}\t\t\tmargin\t\t\t\t组件与其他上下左右元素之间的距离，带单位的字符串形式，如\"30px\"   (默认 '0' )\n\t * @property {Boolean} \t\t\tanimation\t\t\t是否开启动画，见上方说明（默认 false ）\n\t * @property {String}\t\t\tvalue\t\t\t\t输入框初始值\n\t * @property {String | Number}\tmaxlength\t\t\t输入框最大能输入的长度，-1为不限制长度  (默认 '-1' )\n\t * @property {String | Number}\theight\t\t\t\t输入框高度，单位px（默认 64 ）\n\t * @property {String | Number}\tlabel\t\t\t\t搜索框左边显示内容\n\t * @property {Object}\t\t\tcustomStyle\t\t\t定义需要用到的外部样式\n\t *\n\t * @event {Function} change 输入框内容发生变化时触发\n\t * @event {Function} search 用户确定搜索时触发，用户按回车键，或者手机键盘右下角的\"搜索\"键时触发\n\t * @event {Function} custom 用户点击右侧控件时触发\n\t * @event {Function} clear 用户点击清除按钮时触发\n\t * @example <u-search placeholder=\"日照香炉生紫烟\" v-model=\"keyword\"></u-search>\n\t */\n\texport default {\n\t\tname: \"u-search\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tkeyword: '',\n\t\t\t\tshowClear: false, // 是否显示右边的清除图标\n\t\t\t\tshow: false,\n\t\t\t\t// 标记input当前状态是否处于聚焦中，如果是，才会显示右侧的清除控件\n\t\t\t\tfocused: this.focus\n\t\t\t\t// 绑定输入框的值\n\t\t\t\t// inputValue: this.value\n\t\t\t};\n\t\t},\n\t\twatch: {\n\t\t\tkeyword(nVal) {\n\t\t\t\t// 双向绑定值，让v-model绑定的值双向变化\n\t\t\t\tthis.$emit('input', nVal);\n\t\t\t\t// 触发change事件，事件效果和v-model双向绑定的效果一样，让用户多一个选择\n\t\t\t\tthis.$emit('change', nVal);\n\t\t\t},\n\t\t\tvalue: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(nVal) {\n\t\t\t\t\tthis.keyword = nVal;\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tshowActionBtn() {\n\t\t\t\treturn !this.animation && this.showAction\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 目前HX2.6.9 v-model双向绑定无效，故监听input事件获取输入框内容的变化\n\t\t\tinputChange(e) {\n\t\t\t\tthis.keyword = e.detail.value;\n\t\t\t},\n\t\t\t// 清空输入\n\t\t\t// 也可以作为用户通过this.$refs形式调用清空输入框内容\n\t\t\tclear() {\n\t\t\t\tthis.keyword = '';\n\t\t\t\t// 延后发出事件，避免在父组件监听clear事件时，value为更新前的值(不为空)\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tthis.$emit('clear');\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 确定搜索\n\t\t\tsearch(e) {\n\t\t\t\tthis.$emit('search', e.detail.value);\n\t\t\t\ttry {\n\t\t\t\t\t// 收起键盘\n\t\t\t\t\tuni.hideKeyboard();\n\t\t\t\t} catch (e) {}\n\t\t\t},\n\t\t\t// 点击右边自定义按钮的事件\n\t\t\tcustom() {\n\t\t\t\tthis.$emit('custom', this.keyword);\n\t\t\t\ttry {\n\t\t\t\t\t// 收起键盘\n\t\t\t\t\tuni.hideKeyboard();\n\t\t\t\t} catch (e) {}\n\t\t\t},\n\t\t\t// 获取焦点\n\t\t\tgetFocus() {\n\t\t\t\tthis.focused = true;\n\t\t\t\t// 开启右侧搜索按钮展开的动画效果\n\t\t\t\tif (this.animation && this.showAction) this.show = true;\n\t\t\t\tthis.$emit('focus', this.keyword);\n\t\t\t},\n\t\t\t// 失去焦点\n\t\t\tblur() {\n\t\t\t\t// 最开始使用的是监听图标@touchstart事件，自从hx2.8.4后，此方法在微信小程序出错\n\t\t\t\t// 这里改为监听点击事件，手点击清除图标时，同时也发生了@blur事件，导致图标消失而无法点击，这里做一个延时\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tthis.focused = false;\n\t\t\t\t}, 100)\n\t\t\t\tthis.show = false;\n\t\t\t\tthis.$emit('blur', this.keyword);\n\t\t\t},\n\t\t\t// 点击搜索框，只有disabled=true时才发出事件，因为禁止了输入，意味着是想跳转真正的搜索页\n\t\t\tclickHandler() {\n\t\t\t\tif (this.disabled) this.$emit('click');\n\t\t\t},\n\t\t\t// 点击左边图标\n\t\t\tclickIcon() {\n\t\t\t\tthis.$emit('clickIcon');\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n$u-search-content-padding: 0 10px !default;\n$u-search-label-color: $u-main-color !default;\n$u-search-label-font-size: 14px !default;\n$u-search-label-margin: 0 4px !default;\n$u-search-close-size: 20px !default;\n$u-search-close-radius: 100px !default;\n$u-search-close-bgColor: #C6C7CB !default;\n$u-search-close-transform: scale(0.82) !default;\n$u-search-input-font-size: 14px !default;\n$u-search-input-margin: 0 5px !default;\n$u-search-input-color: $u-main-color !default;\n$u-search-input-placeholder-color: $u-tips-color !default;\n$u-search-action-font-size: 14px !default;\n$u-search-action-color: $u-main-color !default;\n$u-search-action-width: 0 !default;\n$u-search-action-active-width: 40px !default;\n$u-search-action-margin-left: 5px !default;\n\n/* #ifdef H5 */\n// iOS15在H5下，hx的某些版本，input type=search时，会多了一个搜索图标，进行移除\n[type=\"search\"]::-webkit-search-decoration {\n    display: none;\n}\n/* #endif */\n\n.u-search {\n\t@include flex(row);\n\talign-items: center;\n\tflex: 1;\n\n\t&__content {\n\t\t@include flex;\n\t\talign-items: center;\n\t\tpadding: $u-search-content-padding;\n\t\tflex: 1;\n\t\tjustify-content: space-between;\n\t\tborder-width: 1px;\n\t\tborder-color: transparent;\n\t\tborder-style: solid;\n\t\toverflow: hidden;\n\n\t\t&__icon {\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\t\t}\n\n\t\t&__label {\n\t\t\tcolor: $u-search-label-color;\n\t\t\tfont-size: $u-search-label-font-size;\n\t\t\tmargin: $u-search-label-margin;\n\t\t}\n\n\t\t&__close {\n\t\t\twidth: $u-search-close-size;\n\t\t\theight: $u-search-close-size;\n\t\t\tborder-top-left-radius: $u-search-close-radius;\n\t\t\tborder-top-right-radius: $u-search-close-radius;\n\t\t\tborder-bottom-left-radius: $u-search-close-radius;\n\t\t\tborder-bottom-right-radius: $u-search-close-radius;\n\t\t\tbackground-color: $u-search-close-bgColor;\n\t\t\t@include flex(row);\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\ttransform: $u-search-close-transform;\n\t\t}\n\n\t\t&__input {\n\t\t\tflex: 1;\n\t\t\tfont-size: $u-search-input-font-size;\n\t\t\tline-height: 1;\n\t\t\tmargin: $u-search-input-margin;\n\t\t\tcolor: $u-search-input-color;\n\n\t\t\t&--placeholder {\n\t\t\t\tcolor: $u-search-input-placeholder-color;\n\t\t\t}\n\t\t}\n\t}\n\n\t&__action {\n\t\tfont-size: $u-search-action-font-size;\n\t\tcolor: $u-search-action-color;\n\t\twidth: $u-search-action-width;\n\t\toverflow: hidden;\n\t\ttransition-property: width;\n\t\ttransition-duration: 0.3s;\n\t\t/* #ifndef APP-NVUE */\n\t\twhite-space: nowrap;\n\t\t/* #endif */\n\t\ttext-align: center;\n\n\t\t&--active {\n\t\t\twidth: $u-search-action-active-width;\n\t\t\tmargin-left: $u-search-action-margin-left;\n\t\t}\n\t}\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-skeleton/props.js",
    "content": "export default {\n    props: {\n        // 是否展示骨架组件\n        loading: {\n            type: Boolean,\n            default: uni.$u.props.skeleton.loading\n        },\n        // 是否开启动画效果\n        animate: {\n            type: Boolean,\n            default: uni.$u.props.skeleton.animate\n        },\n        // 段落占位图行数\n        rows: {\n            type: [String, Number],\n            default: uni.$u.props.skeleton.rows\n        },\n        // 段落占位图的宽度\n        rowsWidth: {\n            type: [String, Number, Array],\n            default: uni.$u.props.skeleton.rowsWidth\n        },\n        // 段落占位图的高度\n        rowsHeight: {\n            type: [String, Number, Array],\n            default: uni.$u.props.skeleton.rowsHeight\n        },\n        // 是否展示标题占位图\n        title: {\n            type: Boolean,\n            default: uni.$u.props.skeleton.title\n        },\n        // 段落标题的宽度\n        titleWidth: {\n            type: [String, Number],\n            default: uni.$u.props.skeleton.titleWidth\n        },\n        // 段落标题的高度\n        titleHeight: {\n            type: [String, Number],\n            default: uni.$u.props.skeleton.titleHeight\n        },\n        // 是否展示头像占位图\n        avatar: {\n            type: Boolean,\n            default: uni.$u.props.skeleton.avatar\n        },\n        // 头像占位图大小\n        avatarSize: {\n            type: [String, Number],\n            default: uni.$u.props.skeleton.avatarSize\n        },\n        // 头像占位图的形状，circle-圆形，square-方形\n        avatarShape: {\n            type: String,\n            default: uni.$u.props.skeleton.avatarShape\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-skeleton/u-skeleton.vue",
    "content": "<template>\n\t<view class=\"u-skeleton\">\n\t\t<view\n\t\t    class=\"u-skeleton__wrapper\"\n\t\t    ref=\"u-skeleton__wrapper\"\n\t\t    v-if=\"loading\"\n\t\t\tstyle=\"display: flex; flex-direction: row;\"\n\t\t>\n\t\t\t<view\n\t\t\t    class=\"u-skeleton__wrapper__avatar\"\n\t\t\t    v-if=\"avatar\"\n\t\t\t    :class=\"[`u-skeleton__wrapper__avatar--${avatarShape}`, animate && 'animate']\"\n\t\t\t    :style=\"{\n\t\t\t\t\t\theight: $u.addUnit(avatarSize),\n\t\t\t\t\t\twidth: $u.addUnit(avatarSize)\n\t\t\t\t\t}\"\n\t\t\t></view>\n\t\t\t<view\n\t\t\t    class=\"u-skeleton__wrapper__content\"\n\t\t\t    ref=\"u-skeleton__wrapper__content\"\n\t\t\t\tstyle=\"flex: 1;\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t    class=\"u-skeleton__wrapper__content__title\"\n\t\t\t\t    v-if=\"title\"\n\t\t\t\t    :style=\"{\n\t\t\t\t\t\t\twidth: uTitleWidth,\n\t\t\t\t\t\t\theight: $u.addUnit(titleHeight),\n\t\t\t\t\t\t}\"\n\t\t\t\t    :class=\"[animate && 'animate']\"\n\t\t\t\t></view>\n\t\t\t\t<view\n\t\t\t\t    class=\"u-skeleton__wrapper__content__rows\"\n\t\t\t\t    :class=\"[animate && 'animate']\"\n\t\t\t\t    v-for=\"(item, index) in rowsArray\"\n\t\t\t\t    :key=\"index\"\n\t\t\t\t    :style=\"{\n\t\t\t\t\t\t\t width: item.width,\n\t\t\t\t\t\t\t height: item.height,\n\t\t\t\t\t\t\t marginTop: item.marginTop\n\t\t\t\t\t\t}\"\n\t\t\t\t>\n\t\t\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t\t<slot v-else />\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\t// 由于weex为阿里的KPI业绩考核的产物，所以不支持百分比单位，这里需要通过dom查询组件的宽度\n\tconst dom = uni.requireNativePlugin('dom')\n\tconst animation = uni.requireNativePlugin('animation')\n\t// #endif\n\t/**\n\t * Skeleton 骨架屏\n\t * @description 骨架屏一般用于页面在请求远程数据尚未完成时，页面用灰色块预显示本来的页面结构，给用户更好的体验。\n\t * @tutorial https://www.uviewui.com/components/skeleton.html\n\t * @property {Boolean}\t\t\t\t\tloading\t\t是否显示骨架占位图，设置为false将会展示子组件内容 (默认 true )\n\t * @property {Boolean}\t\t\t\t\tanimate\t\t是否开启动画效果 (默认 true )\n\t * @property {String | Number}\t\t\trows\t\t段落占位图行数 (默认 0 )\n\t * @property {String | Number | Array}\trowsWidth\t段落占位图的宽度，可以为百分比，数值，带单位字符串等，可通过数组传入指定每个段落行的宽度 (默认 '100%' )\n\t * @property {String | Number | Array}\trowsHeight\t段落的高度 (默认 18 )\n\t * @property {Boolean}\t\t\t\t\ttitle\t\t是否展示标题占位图 (默认 true )\n\t * @property {String | Number}\t\t\ttitleWidth\t标题的宽度 (默认 '50%' )\n\t * @property {String | Number}\t\t\ttitleHeight\t标题的高度 (默认 18 )\n\t * @property {Boolean}\t\t\t\t\tavatar\t\t是否展示头像占位图 (默认 false )\n\t * @property {String | Number}\t\t\tavatarSize\t头像占位图大小 (默认 32 )\n\t * @property {String}\t\t\t\t\tavatarShape\t头像占位图的形状，circle-圆形，square-方形 (默认 'circle' )\n\t * @example <u-search placeholder=\"日照香炉生紫烟\" v-model=\"keyword\"></u-search>\n\t */\n\texport default {\n\t\tname: 'u-skeleton',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\twidth: 0,\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tloading() {\n\t\t\t\tthis.getComponentWidth()\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\trowsArray() {\n\t\t\t\tif (/%$/.test(this.rowsHeight)) {\n\t\t\t\t\tuni.$u.error('rowsHeight参数不支持百分比单位')\n\t\t\t\t}\n\t\t\t\tconst rows = []\n\t\t\t\tfor (let i = 0; i < this.rows; i++) {\n\t\t\t\t\tlet item = {},\n\t\t\t\t\t\t// 需要预防超出数组边界的情况\n\t\t\t\t\t\trowWidth = uni.$u.test.array(this.rowsWidth) ? (this.rowsWidth[i] || (i === this.row - 1 ? '70%' : '100%')) : i ===\n\t\t\t\t\t\tthis.rows - 1 ? '70%' : this.rowsWidth,\n\t\t\t\t\t\trowHeight = uni.$u.test.array(this.rowsHeight) ? (this.rowsHeight[i] || '18px') : this.rowsHeight\n\t\t\t\t\t// 如果有title占位图，第一个段落占位图的外边距需要大一些，如果没有title占位图，第一个段落占位图则无需外边距\n\t\t\t\t\t// 之所以需要这么做，是因为weex的无能，以提升性能为借口不支持css的一些伪类\n\t\t\t\t\titem.marginTop = !this.title && i === 0 ? 0 : this.title && i === 0 ? '20px' : '12px'\n\t\t\t\t\t// 如果设置的为百分比的宽度，转换为px值，因为nvue不支持百分比单位\n\t\t\t\t\tif (/%$/.test(rowWidth)) {\n\t\t\t\t\t\t// 通过parseInt提取出百分比单位中的数值部分，除以100得到百分比的小数值\n\t\t\t\t\t\titem.width = uni.$u.addUnit(this.width * parseInt(rowWidth) / 100)\n\t\t\t\t\t} else {\n\t\t\t\t\t\titem.width = uni.$u.addUnit(rowWidth)\n\t\t\t\t\t}\n\t\t\t\t\titem.height = uni.$u.addUnit(rowHeight)\n\t\t\t\t\trows.push(item)\n\t\t\t\t}\n\t\t\t\t// console.log(rows);\n\t\t\t\treturn rows\n\t\t\t},\n\t\t\tuTitleWidth() {\n\t\t\t\tlet tWidth = 0\n\t\t\t\tif (/%$/.test(this.titleWidth)) {\n\t\t\t\t\t// 通过parseInt提取出百分比单位中的数值部分，除以100得到百分比的小数值\n\t\t\t\t\ttWidth = uni.$u.addUnit(this.width * parseInt(this.titleWidth) / 100)\n\t\t\t\t} else {\n\t\t\t\t\ttWidth = uni.$u.addUnit(this.titleWidth)\n\t\t\t\t}\n\t\t\t\treturn uni.$u.addUnit(tWidth)\n\t\t\t},\n\t\t\t\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tthis.getComponentWidth()\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tthis.loading && this.animate && this.setNvueAnimation()\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\tasync setNvueAnimation() {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// 为了让opacity:1的状态保持一定时间，这里做一个延时\n\t\t\t\tawait uni.$u.sleep(500)\n\t\t\t\tconst skeleton = this.$refs['u-skeleton__wrapper'];\n\t\t\t\tskeleton && this.loading && this.animate && animation.transition(skeleton, {\n\t\t\t\t\tstyles: {\n\t\t\t\t\t\topacity: 0.5\n\t\t\t\t\t},\n\t\t\t\t\tduration: 600,\n\t\t\t\t}, () => {\n\t\t\t\t\t// 这里无需判断是否loading和开启动画状态，因为最终的状态必须达到opacity: 1，否则可能\n\t\t\t\t\t// 会停留在opacity: 0.5的状态中\n\t\t\t\t\tanimation.transition(skeleton, {\n\t\t\t\t\t\tstyles: {\n\t\t\t\t\t\t\topacity: 1\n\t\t\t\t\t\t},\n\t\t\t\t\t\tduration: 600,\n\t\t\t\t\t}, () => {\n\t\t\t\t\t\t// 只有在loading中时，才执行动画\n\t\t\t\t\t\tthis.loading && this.animate && this.setNvueAnimation()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 获取组件的宽度\n\t\t\tasync getComponentWidth() {\n\t\t\t\t// 延时一定时间，以获取dom尺寸\n\t\t\t\tawait uni.$u.sleep(20)\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.$uGetRect('.u-skeleton__wrapper__content').then(size => {\n\t\t\t\t\tthis.width = size.width\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tconst ref = this.$refs['u-skeleton__wrapper__content']\n\t\t\t\tref && dom.getComponentRect(ref, (res) => {\n\t\t\t\t\tthis.width = res.size.width\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t@mixin background {\n\t\t/* #ifdef APP-NVUE */\n\t\tbackground-color: #F1F2F4;\n\t\t/* #endif */\n\t\t/* #ifndef APP-NVUE */\n\t\tbackground: linear-gradient(90deg, #F1F2F4 25%, #e6e6e6 37%, #F1F2F4 50%);\n\t\tbackground-size: 400% 100%;\n\t\t/* #endif */\n\t}\n\n\t.u-skeleton {\n\t\tflex: 1;\n\t\t\n\t\t&__wrapper {\n\t\t\t@include flex(row);\n\t\t\t\n\t\t\t&__avatar {\n\t\t\t\t@include background;\n\t\t\t\tmargin-right: 15px;\n\t\t\t\n\t\t\t\t&--circle {\n\t\t\t\t\tborder-radius: 100px;\n\t\t\t\t}\n\t\t\t\n\t\t\t\t&--square {\n\t\t\t\t\tborder-radius: 4px;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t&__content {\n\t\t\t\tflex: 1;\n\t\t\t\n\t\t\t\t&__rows,\n\t\t\t\t&__title {\n\t\t\t\t\t@include background;\n\t\t\t\t\tborder-radius: 3px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/* #ifndef APP-NVUE */\n\t.animate {\n\t\tanimation: skeleton 1.8s ease infinite\n\t}\n\n\t@keyframes skeleton {\n\t\t0% {\n\t\t\tbackground-position: 100% 50%\n\t\t}\n\n\t\t100% {\n\t\t\tbackground-position: 0 50%\n\t\t}\n\t}\n\n\t/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-slider/mpother.js",
    "content": "/**\n * 使用普通的js方案实现slider\n */\nexport default {\n    watch: {\n        value(n) {\n            // 只有在非滑动状态时，才可以通过value更新滑块值，这里监听，是为了让用户触发\n            if (this.status === 'end') {\n                this.updateSliderPlacement(n, true)\n            }\n        }\n    },\n    mounted() {\n        this.init()\n    },\n    methods: {\n        init() {\n            this.getSliderRect()\n        },\n        // 获取slider尺寸\n        getSliderRect() {\n            // 获取滑块条的尺寸信息\n            setTimeout(() => {\n                this.$uGetRect('.u-slider').then((rect) => {\n                    this.sliderRect = rect\n                    this.updateSliderPlacement(this.value, true)\n                })\n            }, 10)\n        },\n        // 是否可以操作\n        canNotDo() {\n            return this.disabled\n        },\n        // 获取当前手势点的X轴位移值\n        getTouchX(e) {\n            return e.touches[0].clientX\n        },\n        formatStep(value) {\n            // 移动点占总长度的百分比\n            return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step\n        },\n        // 发出事件\n        emitEvent(event, value) {\n            this.$emit(event, value || this.value)\n        },\n        // 标记当前手势的状态\n        setTouchStatus(status) {\n            this.status = status\n        },\n        onTouchStart(e) {\n            if (this.canNotDo()) {\n                return\n            }\n            // 标示当前的状态为开始触摸滑动\n            this.emitEvent('start')\n            this.setTouchStatus('start')\n        },\n        onTouchMove(e) {\n            if (this.canNotDo()) {\n                return\n            }\n            // 滑块的左边不一定跟屏幕左边接壤，所以需要减去最外层父元素的左边值\n            const x = this.getTouchX(e)\n            const { left, width } = this.sliderRect\n            const distanceX = x - left\n            // 获得移动距离对整个滑块的百分比值，此为带有多位小数的值，不能用此更新视图\n            // 否则造成通信阻塞，需要每改变一个step值时修改一次视图\n            const percent = (distanceX / width) * 100\n            this.setTouchStatus('moving')\n            this.updateSliderPlacement(percent, true, 'moving')\n        },\n        onTouchEnd() {\n            if (this.canNotDo()) {\n                return\n            }\n            this.emitEvent('end')\n            this.setTouchStatus('end')\n        },\n        // 设置滑点的位置\n        updateSliderPlacement(value, drag, event) {\n            // 去掉小数部分，同时也是对step步进的处理\n            const { width } = this.sliderRect\n            const percent = this.formatStep(value)\n            // 设置移动的值\n            const barStyle = {\n                width: `${percent / 100 * width}px`\n            }\n            // 移动期间无需过渡动画\n            if (drag === true) {\n                barStyle.transition = 'none'\n            } else {\n                // 非移动期间，删掉对过渡为空的声明，让css中的声明起效\n                delete barStyle.transition\n            }\n            // 修改value值\n            this.$emit('input', percent)\n            // 事件的名称\n            if (event) {\n                this.emitEvent(event, percent)\n            }\n            this.barStyle = barStyle\n        },\n        onClick(e) {\n            if (this.canNotDo()) {\n                return\n            }\n            // 直接点击滑块的情况，计算方式与onTouchMove方法相同\n            const { left, width } = this.sliderRect\n            const value = ((e.detail.x - left) / width) * 100\n            this.updateSliderPlacement(value, false, 'click')\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-slider/mpwxs.js",
    "content": "export default {\n    data() {\n        return {\n            sliderRect: {},\n            info: {\n                width: null,\n                left: null,\n                step: this.step,\n                disabled: this.disabled,\n                min: this.min,\n                max: this.max,\n                value: this.value\n            }\n        }\n    },\n    mounted() {\n        this.init()\n    },\n    methods: {\n        init() {\n            this.getSliderRect()\n        },\n        // 获取slider尺寸\n        getSliderRect() {\n            // 获取滑块条的尺寸信息\n            uni.$u.sleep().then(() => {\n                this.$uGetRect('.u-slider').then((rect) => {\n                    this.info.width = rect.width\n                    this.info.left = rect.left\n                })\n            })\n        },\n        // 此方法由wxs调用，用于修改v-model绑定的值\n        updateValue(value) {\n            this.$emit('input', value)\n        },\n        // 此方法由wxs调用，发出事件\n        emitEvent(e) {\n            this.$emit(e.event, e.value ? e.value : this.value)\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-slider/mpwxs.wxs",
    "content": "/**\n * 使用wxs方案实现slider\n * 兼容微信，QQ，H5，Vue版的安卓和iOS\n */\n/**\n * 开始滑动操作\n * @param {Object} e\n * @param {Object} ownerInstance\n */\nfunction onTouchMove(e, ownerInstance) {\n\t// wxs事件对象下有一个instance属性，表示当前触发此事件的组件的实例，通过该实例，可以获取相关的dataset，设置样式等信息\n\t// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html\n\tvar instance = e.instance;\n\t// getState()为一个对象，挂载在instance上，类似组件的data一样，可以存放一些变量，供以后的触发事件中使用\n\tvar state = instance.getState()\n\n\t// 滑块组件的整体尺寸信息\n\tvar mp = state.mp\n\tif(mp.disabled) {\n\t\treturn\n\t}\n\t\n\tvar distanceX = getTouchX(e) - mp.left\n\t// 获得移动距离对整个滑块的百分比值，此为带有多位小数的值，step大于1时，不能用此更新视图\n\tvar percent = (distanceX / mp.width) * 100\n\n\tupdateSliderPlacement(instance, ownerInstance, percent, 'moving')\n\t\n\t// 阻止页面滚动，可以保证在滑动过程中，不让页面可以上下滚动，造成不好的体验\n\te.stopPropagation && e.stopPropagation() \n\te.preventDefault && e.preventDefault()\n}\n\nfunction onClick(e, ownerInstance) {\n\tvar instance = e.instance\n\tvar state = instance.getState()\n\tvar mp = state.mp\n\tif(mp.disabled) {\n\t\treturn\n\t}\n\t\n\t// 直接点击滑块的情况，计算方式与onTouchMove方法相同\n\tvar value = ((e.detail.x - mp.left) / mp.width) * 100\n\tupdateSliderPlacement(instance, ownerInstance, value, 'click')\n}\n\nfunction sizeReady(newValue, oldValue, ownerInstance, instance) {\n\t// 页面初始化时候，也会触发此方法，传递的值为空，这里不执行往后的逻辑\n\tif(!newValue || newValue.disabled) {\n\t\treturn \n\t}\n\tvar state = instance.getState()\n\tstate.mp = newValue\n\tupdateSliderPlacement(instance, ownerInstance, newValue.value)\n}\n\n// 设置滑点的位置\nfunction updateSliderPlacement(instance, ownerInstance, value, event) {\n\tvar state = instance.getState()\n\tvar mp = state.mp\n\tif(mp.disabled) {\n\t\treturn\n\t}\n\n\tvar percent = 0\n\tif (mp.step > 1) {\n\t\t// 如果step步进大于1，需要跳步，所以需要使用Math.round进行取整\n\t\tpercent = Math.round(Math.max(mp.min, Math.min(value, mp.max)) / mp.step) * mp.step\n\t} else {\n\t\t// 当step=1时，无需跳步，充分利用wxs性能，滑块实时跟随手势，达到丝滑的效果\n\t\tpercent = Math.max(mp.min, Math.min(value, mp.max))\n\t}\n\t// 返回组件的实例\n\tvar gapInstance = ownerInstance.selectComponent('.u-slider__gap')\n\t// 在移动期间，不允许transition动画，否则会造成卡顿\n\tgapInstance[event === 'click' ? 'addClass' : 'removeClass']('u-slider__gap--ani')\n\t// 调用逻辑层的方法，修改v-model绑定的值\n\townerInstance.callMethod('updateValue', Math.round(percent))\n\tif(event) {\n\t\townerInstance.callMethod('emitEvent', {\n\t\t\tevent: event,\n\t\t\tvalue: Math.round(percent)\n\t\t})\n\t}\n\t\n\t// 设置移动的值\n\tgapInstance.requestAnimationFrame(function() {\n\t\tgapInstance.setStyle({\n\t\t\twidth: percent / 100 * mp.width + 'px',\n\t\t})\n\t})\n}\n\n// 开始滑动\nfunction onTouchStart(e, ownerInstance) {\n\townerInstance.callMethod('emitEvent', {\n\t\tevent: 'start', \n\t\tvalue: null\n\t})\n}\n\n// 停止滑动\nfunction onTouchEnd(e, ownerInstance) {\n\townerInstance.callMethod('emitEvent', {\n\t\tevent: 'end', \n\t\tvalue: null\n\t})\n}\n\n// 获取当前手势点的X轴位移值\nfunction getTouchX(e) {\n\treturn e.touches[0].clientX\n}\n\nmodule.exports = {\n\tonTouchStart: onTouchStart,\n\tonTouchMove: onTouchMove,\n\tonTouchEnd: onTouchEnd,\n\tsizeReady: sizeReady,\n\tonClick: onClick\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-slider/nvue - 副本.js",
    "content": "/**\n * 使用bindingx方案实现slider\n * 只能使用于nvue下\n */\n// 引入bindingx，此库类似于微信小程序wxs，目的是让js运行在视图层，减少视图层和逻辑层的通信折损\nconst BindingX = uni.requireNativePlugin('bindingx')\n// nvue操作dom的库，用于获取dom的尺寸信息\nconst dom = uni.requireNativePlugin('dom')\n// nvue中用于操作元素动画的库，类似于uni.animation，只不过uni.animation不能用于nvue\nconst animation = uni.requireNativePlugin('animation')\n\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\t// bindingx的回调值，用于取消绑定\n\t\t\tpanEvent: null,\n\t\t\t// 标记是否移动状态\n\t\t\tmoving: false,\n\t\t\t// 位移的偏移量\n\t\t\tx: 0,\n\t\t\t// 是否正在触摸过程中，用于标记动画类是否添加或移除\n\t\t\ttouching: false,\n\t\t\tchangeFromInside: false\n\t\t}\n\t},\n\twatch: {\n\t\t// 监听vlaue的变化，此变化可能是由于内部修改v-model的值，或者外部\n\t\t// 从服务端获取一个值后，赋值给slider的v-model而导致的\n\t\tvalue(n) {\n\t\t\tif (!this.changeFromInside) {\n\t\t\t\tthis.initX()\n\t\t\t} else {\n\t\t\t\tthis.changeFromInside = false\n\t\t\t}\n\t\t}\n\t},\n\tmounted() {\n\t\tthis.init()\n\t},\n\tmethods: {\n\t\tinit() {\n\t\t\tthis.getSliderRect()\n\t\t},\n\t\t// 获取节点信息\n\t\t// 获取slider尺寸\n\t\tgetSliderRect() {\n\t\t\t// 获取滑块条的尺寸信息\n\t\t\t// 通过nvue的dom模块，查询节点信息\n\t\t\tsetTimeout(() => {\n\t\t\t\tdom.getComponentRect(this.$refs['slider'], res => {\n\t\t\t\t\tthis.sliderRect = res.size\n\t\t\t\t\tthis.initX()\n\t\t\t\t})\n\t\t\t}, 10)\n\t\t},\n\t\t// 初始化按钮位置\n\t\tinitButtonStyle({\n\t\t\tbarStyle,\n\t\t\tbuttonWrapperStyle\n\t\t}) {\n\t\t\tthis.barStyle = barStyle\n\t\t\tthis.buttonWrapperStyle = buttonWrapperStyle\n\t\t},\n\t\temitEvent(event, value) {\n\t\t\tthis.$emit(event, value ? value : this.value)\n\t\t},\n\t\tformatStep(value) {\n\t\t\t// 移动点占总长度的百分比\n\t\t\treturn Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step\n\t\t},\n\t\t// 滑动开始\n\t\tonTouchStart(e) {\n\t\t\t// 阻止页面滚动，可以保证在滑动过程中，不让页面可以上下滚动，造成不好的体验\n\t\t\te.stopPropagation && e.stopPropagation()\n\t\t\te.preventDefault && e.preventDefault()\n\t\t\tif (this.moving || this.disabled) {\n\t\t\t\t// 释放上一次的资源\n\t\t\t\tif (this.panEvent?.token != 0) {\n\t\t\t\t\tBindingX.unbind({\n\t\t\t\t\t\ttoken: this.panEvent.token,\n\t\t\t\t\t\t// pan为手势事件\n\t\t\t\t\t\teventType: 'pan'\n\t\t\t\t\t})\n\t\t\t\t\tthis.gesToken = 0\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tthis.moving = true\n\t\t\tthis.touching = true\n\n\t\t\t// 获取元素ref\n\t\t\tconst button = this.$refs['nvue-button'].ref\n\t\t\tconst gap = this.$refs['nvue-gap'].ref\n\n\t\t\tconst {\n\t\t\t\tmin,\n\t\t\t\tmax,\n\t\t\t\tstep\n\t\t\t} = this\n\t\t\tconst {\n\t\t\t\tleft,\n\t\t\t\twidth\n\t\t\t} = this.sliderRect\n\n\t\t\t// 初始值为本次偏移量x，加上次停止滑动时的结束值\n\t\t\tlet exporession = `(${this.x} + x)`\n\t\t\t// 将偏移的x值，转为总位移的百分比值，为了和min和max进行判断\n\t\t\texporession = `(${exporession} / ${width}) * 100`\n\t\t\tif (step > 1) {\n\t\t\t\t// 如果step步进大于1，需要跳步，所以需要使用Math.round进行取整\n\t\t\t\texporession = `round(max(${min}, min(${exporession}, ${max})) / ${step}) * ${step}`\n\t\t\t} else {\n\t\t\t\t// 当step=1时，无需跳步，充分利用bindingx性能，滑块实时跟随手势，达到丝滑的效果\n\t\t\t\texporession = `max(${min}, min(${exporession}, ${max}))`\n\t\t\t}\n\t\t\t// 将百分比最后转化为对应的px值\n\t\t\texporession = `${exporession} / 100 * ${width}`\n\t\t\t// 最大值不允许超过轨迹的宽度\n\t\t\tconst {\n\t\t\t\tsliderWidth\n\t\t\t} = this.sliderRect\n\t\t\texporession = `min(${sliderWidth}, ${exporession})`\n\t\t\t// 滑块点总是需要一个左偏移的值，为自身宽度的一半\n\t\t\tconst buttonExpression = `${exporession} - ${this.blockHeight / 2}`\n\t\t\t// 阿里为了KPI而开源的BindingX\n\t\t\tthis.panEvent = BindingX.bind({\n\t\t\t\tanchor: button,\n\t\t\t\teventType: 'pan',\n\t\t\t\tprops: [{\n\t\t\t\t\telement: gap,\n\t\t\t\t\t// 绑定width属性，设置其宽度值\n\t\t\t\t\tproperty: 'width',\n\t\t\t\t\texpression\n\t\t\t\t}, {\n\t\t\t\t\telement: button,\n\t\t\t\t\t// 绑定width属性，设置其宽度值\n\t\t\t\t\tproperty: 'transform.translateX',\n\t\t\t\t\texpression: buttonExpression\n\t\t\t\t}]\n\t\t\t}, (e) => {\n\t\t\t\tif (e.state === 'end' || e.state === 'exit') {\n\t\t\t\t\t// \n\t\t\t\t\tthis.x = uni.$u.range(0, left + width, e.deltaX + this.x)\n\t\t\t\t\t// 根据偏移值，得出移动的百分比，进而修改双向绑定的v-model的值\n\t\t\t\t\tconst value = (this.x / width) * 100\n\t\t\t\t\tconst percent = this.formatStep(value)\n\t\t\t\t\t// 修改value值\n\t\t\t\t\tthis.$emit('input', percent)\n\t\t\t\t\t// 标记下一次触发value的watch时，这个值的变化，是由内部改变的\n\t\t\t\t\tthis.changeFromInside = true\n\t\t\t\t\tthis.moving = false\n\t\t\t\t\tthis.touching = false\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\t// 从value的变化，倒推得出x的值该为多少\n\t\tinitX() {\n\t\t\tconst {\n\t\t\t\tleft,\n\t\t\t\twidth\n\t\t\t} = this.sliderRect\n\t\t\t// 得出x的初始偏移值，之所以需要这么做，是因为在bindingX中，触摸滑动时，只能的值本次移动的偏移值\n\t\t\t// 而无法的值准确的前后移动的两个点的坐标值，weex纯粹为阿里巴巴的KPI(部门业绩考核)产物，也就这样了\n\t\t\tthis.x = this.value / 100 * width\n\t\t\t// 设置移动的值\n\t\t\tconst barStyle = {\n\t\t\t\twidth: this.x + 'px'\n\t\t\t}\n\t\t\t// 按钮的初始值\n\t\t\tconst buttonWrapperStyle = {\n\t\t\t\ttransform: `translateX(${this.x - this.blockHeight / 2}px)`\n\t\t\t}\n\t\t\tthis.initButtonStyle({\n\t\t\t\tbarStyle,\n\t\t\t\tbuttonWrapperStyle\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-slider/nvue.js",
    "content": "/**\n * 使用bindingx方案实现slider\n * 只能使用于nvue下\n */\n// 引入bindingx，此库类似于微信小程序wxs，目的是让js运行在视图层，减少视图层和逻辑层的通信折损\nconst BindingX = uni.requireNativePlugin('bindingx')\n// nvue操作dom的库，用于获取dom的尺寸信息\nconst dom = uni.requireNativePlugin('dom')\n// nvue中用于操作元素动画的库，类似于uni.animation，只不过uni.animation不能用于nvue\nconst animation = uni.requireNativePlugin('animation')\n\nexport default {\n    data() {\n        return {\n            // 位移的偏移量\n            x: 0,\n            // 是否正在触摸过程中，用于标记动画类是否添加或移除\n            touching: false,\n            changeFromInside: false\n        }\n    },\n    watch: {\n        // 监听vlaue的变化，此变化可能是由于内部修改v-model的值，或者外部\n        // 从服务端获取一个值后，赋值给slider的v-model而导致的\n        value(n) {\n            if (!this.changeFromInside) {\n                this.initX()\n            } else {\n                this.changeFromInside = false\n            }\n        }\n    },\n    mounted() {\n        this.init()\n    },\n    methods: {\n        init() {\n            // 更新滑块尺寸信息\n            this.getSliderRect().then((size) => {\n                this.sliderRect = size\n                this.initX()\n            })\n        },\n        // 获取节点信息\n        // 获取slider尺寸\n        getSliderRect() {\n            // 获取滑块条的尺寸信息\n            // 通过nvue的dom模块，查询节点信息\n            return new Promise((resolve) => {\n                this.$nextTick(() => {\n                    dom.getComponentRect(this.$refs.slider, (res) => {\n                        resolve(res.size)\n                    })\n                })\n            })\n        },\n        // 初始化按钮位置\n        initButtonStyle({\n            barStyle,\n            buttonWrapperStyle\n        }) {\n            this.barStyle = barStyle\n            this.buttonWrapperStyle = buttonWrapperStyle\n        },\n        emitEvent(event, value) {\n            this.$emit(event, value || this.value)\n        },\n        // 滑动开始\n        async onTouchStart(e) {\n            // if (this.disabled) return\n            // // 阻止页面滚动，可以保证在滑动过程中，不让页面可以上下滚动，造成不好的体验\n            // e.stopPropagation && e.stopPropagation()\n            // e.preventDefault && e.preventDefault()\n            // // 更新滑块的尺寸信息\n            // this.sliderRect = await this.getSliderRect()\n            // // 标记滑动过程中触摸点的信息\n            // this.touchStart(e)\n            // this.startValue = this.format(this.value)\n            // this.dragStatus = 'start'\n\n            // 标记滑动过程中触摸点的信息\n            // this.touchStart(e)\n        },\n        // 开始滑动\n        onTouchMove(e) {\n            // if (this.disabled) return;\n            // if (this.dragStatus === 'start') {\n            // \tthis.$emit('drag-start')\n            // }\n            // // 标记当前滑动过程中的触点信息，此方法在touch mixin中\n            // this.touchMove(e)\n            // this.dragStatus = 'draging'\n            // const {\n            // \twidth: sliderWidth\n            // } = this.sliderRect\n            // const diff = (this.deltaX / sliderWidth) * this.getRange()\n            // this.newValue = this.startValue + diff\n            // this.updateValue(this.newValue, false, true)\n            // 获取元素ref\n            // const button = this.$refs['nvue-button'].ref\n            // const gap = this.$refs['nvue-gap'].ref\n\n            //          animation.transition(gap, {\n            // \tstyles: {\n            //                  width: `${this.startX + this.deltaX}px`\n            // \t}\n            // })\n            // // console.log(this.startX + this.deltaX);\n            // animation.transition(button, {\n            // \tstyles: {\n            //         transform: `translateX(${this.startX + this.deltaX}px)`\n            // \t}\n            // })\n            // this.barStyle = {\n            // \twidth: `${this.startX + this.deltaX}px`\n            // }\n            const {\n                x\n            } = this.getTouchPoint(e)\n            this.buttonWrapperStyle = {\n                transform: `translateX(${x}px)`\n            }\n            // this.buttonWrapperStyle = {\n            // \ttransform: `translateX(${this.format(this.startX + this.deltaX)}px)`\n            // }\n        },\n        // onTouchEnd() {\n        // \tif (this.disabled) return;\n        // \tif (this.dragStatus === 'draging') {\n        // \t\tthis.updateValue(this.newValue, true)\n        // \t\tthis.$emit('drag-end');\n        // \t}\n        // },\n        updateValue(value, end, drag) {\n            value = this.format(value)\n            const {\n                width: sliderWidth\n            } = this.sliderRect\n            const width = `${((value - this.min) * sliderWidth) / this.getRange()}`\n            this.value = value\n            this.barStyle = {\n                width: `${width}px`\n            }\n            // console.log('width', width);\n            if (drag) {\n                this.$emit('drag', {\n                    value\n                })\n            }\n            if (end) {\n                this.$emit('change', value)\n            }\n            if ((drag || end)) {\n                this.changeFromInside = true\n                this.$emit('update', value)\n            }\n        },\n        // 从value的变化，倒推得出x的值该为多少\n        initX() {\n            const {\n                left,\n                width\n            } = this.sliderRect\n            // 得出x的初始偏移值，之所以需要这么做，是因为在bindingX中，触摸滑动时，只能的值本次移动的偏移值\n            // 而无法的值准确的前后移动的两个点的坐标值，weex纯粹为阿里巴巴的KPI(部门业绩考核)产物，也就这样了\n            this.x = this.value / 100 * width\n            // 设置移动的值\n            const barStyle = {\n                width: `${this.x}px`\n            }\n            // 按钮的初始值\n            const buttonWrapperStyle = {\n                transform: `translateX(${this.x - this.blockHeight / 2}px)`\n            }\n            this.initButtonStyle({\n                barStyle,\n                buttonWrapperStyle\n            })\n        },\n        // 移动点占总长度的百分比，此处需要先除以step，是为了保证step大于1时，比如10，那么在滑动11,12px这样的\n        // 距离时，实际上滑块是不会滑动的，到了16,17px，经过四舍五入后，就变成了20px，进行了下一个跳变\n        format(value) {\n            return Math.round(uni.$u.range(this.min, this.max, value) / this.step) * this.step\n        },\n        getRange() {\n            const {\n                max,\n                min\n            } = this\n            return max - min\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-slider/props.js",
    "content": "export default {\n    props: {\n        // 最小可选值\n        min: {\n            type: [Number, String],\n            default: uni.$u.props.slider.min\n        },\n        // 最大可选值\n        max: {\n            type: [Number, String],\n            default: uni.$u.props.slider.max\n        },\n        // 步长，取值必须大于 0，并且可被(max - min)整除\n        step: {\n            type: [Number, String],\n            default: uni.$u.props.slider.step\n        },\n        // 当前取值\n        value: {\n            type: [Number, String],\n            default: uni.$u.props.slider.value\n        },\n        // 滑块右侧已选择部分的背景色\n        activeColor: {\n            type: String,\n            default: uni.$u.props.slider.activeColor\n        },\n        // 滑块左侧未选择部分的背景色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.slider.inactiveColor\n        },\n        // 滑块的大小，取值范围为 12 - 28\n        blockSize: {\n            type: [Number, String],\n            default: uni.$u.props.slider.blockSize\n        },\n        // 滑块的颜色\n        blockColor: {\n            type: String,\n            default: uni.$u.props.slider.blockColor\n        },\n\t\t// 禁用状态\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.slider.disabled\n\t\t},\n        // 是否显示当前的选择值\n        showValue: {\n            type: Boolean,\n            default: uni.$u.props.slider.showValue\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-slider/u-slider.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-slider\"\n\t\t:style=\"[$u.addStyle(customStyle)]\"\n\t>\n\t\t<slider\n\t\t\t:min=\"min\"\n\t\t\t:max=\"max\"\n\t\t\t:step=\"step\"\n\t\t\t:value=\"value\"\n\t\t\t:activeColor=\"activeColor\"\n\t\t\t:inactiveColor=\"inactiveColor\"\n\t\t\t:blockSize=\"$u.getPx(blockSize)\"\n\t\t\t:blockColor=\"blockColor\"\n\t\t\t:showValue=\"showValue\"\n\t\t\t:disabled=\"disabled\"\n\t\t\t@changing=\"changingHandler\"\n\t\t\t@change=\"changeHandler\"\n\t\t></slider>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js'\n\texport default {\n\t\tname: 'u--slider',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tmethods: {\n\t\t\t// 拖动过程中触发\n\t\t\tchangingHandler(e) {\n\t\t\t\tconst {\n\t\t\t\t\tvalue\n\t\t\t\t} = e.detail\n\t\t\t\t// 更新v-model的值\n\t\t\t\tthis.$emit('input', value)\n\t\t\t\t// 触发事件\n\t\t\t\tthis.$emit('changing', value)\n\t\t\t},\n\t\t\t// 滑动结束时触发\n\t\t\tchangeHandler(e) {\n\t\t\t\tconst {\n\t\t\t\t\tvalue\n\t\t\t\t} = e.detail\n\t\t\t\t// 更新v-model的值\n\t\t\t\tthis.$emit('input', value)\n\t\t\t\t// 触发事件\n\t\t\t\tthis.$emit('change', value)\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-status-bar/props.js",
    "content": "export default {\n    props: {\n        bgColor: {\n            type: String,\n            default: uni.$u.props.statusBar.bgColor\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-status-bar/u-status-bar.vue",
    "content": "<template>\n\t<view\n\t    :style=\"[style]\"\n\t    class=\"u-status-bar\"\n\t>\n\t\t<slot />\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * StatbusBar 状态栏占位\n\t * @description 本组件主要用于状态填充，比如在自定导航栏的时候，它会自动适配一个恰当的状态栏高度。\n\t * @tutorial https://uviewui.com/components/statusBar.html\n\t * @property {String}\t\t\tbgColor\t\t\t背景色 (默认 'transparent' )\n\t * @property {String | Object}\tcustomStyle\t\t自定义样式 \n\t * @example <u-status-bar></u-status-bar>\n\t */\n\texport default {\n\t\tname: 'u-status-bar',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tstyle() {\n\t\t\t\tconst style = {}\n\t\t\t\t// 状态栏高度，由于某些安卓和微信开发工具无法识别css的顶部状态栏变量，所以使用js获取的方式\n\t\t\t\tstyle.height = uni.$u.addUnit(uni.$u.sys().statusBarHeight, 'px')\n\t\t\t\tstyle.backgroundColor = this.bgColor\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t.u-status-bar {\n\t\t// nvue会默认100%，如果nvue下，显式写100%的话，会导致宽度不为100%而异常\n\t\t/* #ifndef APP-NVUE */\n\t\twidth: 100%;\n\t\t/* #endif */\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-steps/props.js",
    "content": "export default {\n    props: {\n        // 排列方向\n        direction: {\n            type: String,\n            default: uni.$u.props.steps.direction\n        },\n        // 设置第几个步骤\n        current: {\n            type: [String, Number],\n            default: uni.$u.props.steps.current\n        },\n        // 激活状态颜色\n        activeColor: {\n            type: String,\n            default: uni.$u.props.steps.activeColor\n        },\n        // 未激活状态颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.steps.inactiveColor\n        },\n        // 激活状态的图标\n        activeIcon: {\n            type: String,\n            default: uni.$u.props.steps.activeIcon\n        },\n        // 未激活状态图标\n        inactiveIcon: {\n            type: String,\n            default: uni.$u.props.steps.inactiveIcon\n        },\n        // 是否显示点类型\n        dot: {\n            type: Boolean,\n            default: uni.$u.props.steps.dot\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-steps/u-steps.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-steps\"\n\t    :class=\"[`u-steps--${direction}`]\"\n\t>\n\t\t<slot></slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Steps 步骤条\n\t * @description 该组件一般用于完成一个任务要分几个步骤，标识目前处于第几步的场景。\n\t * @tutorial https://uviewui.com/components/steps.html\n\t * @property {String}\t\t\tdirection\t\trow-横向，column-竖向 (默认 'row' )\n\t * @property {String | Number}\tcurrent\t\t\t设置当前处于第几步 (默认 0 )\n\t * @property {String}\t\t\tactiveColor\t\t激活状态颜色 (默认 '#3c9cff' )\n\t * @property {String}\t\t\tinactiveColor\t未激活状态颜色 (默认 '#969799' )\n\t * @property {String}\t\t\tactiveIcon\t\t激活状态的图标\n\t * @property {String}\t\t\tinactiveIcon\t未激活状态图标 \n\t * @property {Boolean}\t\t\tdot\t\t\t\t是否显示点类型 (默认 false )\n\t * @example <u-steps current=\"0\"><u-steps-item title=\"已出库\" desc=\"10:35\" ></u-steps-item></u-steps>\n\t */\n\texport default {\n\t\tname: 'u-steps',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tchildren() {\n\t\t\t\tthis.updateChildData()\n\t\t\t},\n\t\t\tparentData() {\n\t\t\t\tthis.updateChildData()\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 监听参数的变化，通过watch中，手动去更新子组件的数据，否则子组件不会自动变化\n\t\t\tparentData() {\n\t\t\t\treturn [this.current, this.direction, this.activeColor, this.inactiveColor, this.activeIcon, this.inactiveIcon, this.dot]\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 更新子组件的数据\n\t\t\tupdateChildData() {\n\t\t\t\tthis.children.map(child => {\n\t\t\t\t\t// 先判断子组件是否存在对应的方法\n\t\t\t\t\tuni.$u.test.func((child || {}).updateFromParent()) && child.updateFromParent()\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 接受子组件的通知，去修改其他子组件的数据\n\t\t\tupdateFromChild() {\n\t\t\t\tthis.updateChildData()\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.children = []\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-steps {\n\t\t@include flex;\n\n\t\t&--column {\n\t\t\tflex-direction: column\n\t\t}\n\n\t\t&--row {\n\t\t\tflex-direction: row;\n\t\t\tflex: 1;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-steps-item/props.js",
    "content": "export default {\n    props: {\n        // 标题\n        title: {\n            type: [String, Number],\n            default: uni.$u.props.stepsItem.title\n        },\n        // 描述文本\n        desc: {\n            type: [String, Number],\n            default: uni.$u.props.stepsItem.desc\n        },\n        // 图标大小\n        iconSize: {\n            type: [String, Number],\n            default: uni.$u.props.stepsItem.iconSize\n        },\n        // 当前步骤是否处于失败状态\n        error: {\n            type: Boolean,\n            default: uni.$u.props.stepsItem.error\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-steps-item/u-steps-item.vue",
    "content": "<template>\n\t<view class=\"u-steps-item\" ref=\"u-steps-item\" :class=\"[`u-steps-item--${parentData.direction}`]\">\n\t\t<view class=\"u-steps-item__line\" v-if=\"index + 1 < childLength\"\n\t\t\t:class=\"[`u-steps-item__line--${parentData.direction}`]\" :style=\"[lineStyle]\"></view>\n\t\t<view class=\"u-steps-item__wrapper\"\n\t\t\t:class=\"[`u-steps-item__wrapper--${parentData.direction}`, parentData.dot && `u-steps-item__wrapper--${parentData.direction}--dot`]\">\n\t\t\t<slot name=\"icon\">\n\t\t\t\t<view class=\"u-steps-item__wrapper__dot\" v-if=\"parentData.dot\" :style=\"{\n\t\t\t\t\t\tbackgroundColor: statusColor\n\t\t\t\t\t}\">\n\n\t\t\t\t</view>\n\t\t\t\t<view class=\"u-steps-item__wrapper__icon\" v-else-if=\"parentData.activeIcon || parentData.inactiveIcon\">\n\t\t\t\t\t<u-icon :name=\"index <= parentData.current ? parentData.activeIcon : parentData.inactiveIcon\"\n\t\t\t\t\t\t:size=\"iconSize\"\n\t\t\t\t\t\t:color=\"index <= parentData.current ? parentData.activeColor : parentData.inactiveColor\">\n\t\t\t\t\t</u-icon>\n\t\t\t\t</view>\n\t\t\t\t<view v-else :style=\"{\n\t\t\t\t\t\tbackgroundColor: statusClass === 'process' ? parentData.activeColor : 'transparent',\n\t\t\t\t\t\tborderColor: statusColor\n\t\t\t\t\t}\" class=\"u-steps-item__wrapper__circle\">\n\t\t\t\t\t<text v-if=\"statusClass === 'process' || statusClass === 'wait'\"\n\t\t\t\t\t\tclass=\"u-steps-item__wrapper__circle__text\" :style=\"{\n\t\t\t\t\t\t\tcolor: index == parentData.current ? '#ffffff' : parentData.inactiveColor\n\t\t\t\t\t\t}\">{{ index + 1}}</text>\n\t\t\t\t\t<u-icon v-else :color=\"statusClass === 'error' ? 'error' : parentData.activeColor\" size=\"12\"\n\t\t\t\t\t\t:name=\"statusClass === 'error' ? 'close' : 'checkmark'\"></u-icon>\n\t\t\t\t</view>\n\t\t\t</slot>\n\t\t</view>\n\t\t<view class=\"u-steps-item__content\" :class=\"[`u-steps-item__content--${parentData.direction}`]\"\n\t\t\t:style=\"[contentStyle]\">\n\t\t\t<u--text :text=\"title\" :type=\"parentData.current == index ? 'main' : 'content'\" lineHeight=\"20px\"\n\t\t\t\t:size=\"parentData.current == index ? 14 : 13\"></u--text>\n\t\t\t<slot name=\"desc\">\n\t\t\t\t<u--text :text=\"desc\" type=\"tips\" size=\"12\"></u--text>\n\t\t\t</slot>\n\t\t</view>\n\t\t<!-- <view\n\t\t    class=\"u-steps-item__line\"\n\t\t    v-if=\"showLine && parentData.direction === 'column'\"\n\t\t\t:class=\"[`u-steps-item__line--${parentData.direction}`]\"\n\t\t    :style=\"[lineStyle]\"\n\t\t></view> -->\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * StepsItem 步骤条的子组件\n\t * @description 本组件需要和u-steps配合使用\n\t * @tutorial https://uviewui.com/components/steps.html\n\t * @property {String}\t\t\ttitle\t\t\t标题文字\n\t * @property {String}\t\t\tcurrent\t\t\t描述文本\n\t * @property {String | Number}\ticonSize\t\t图标大小  (默认 17 )\n\t * @property {Boolean}\t\t\terror\t\t\t当前步骤是否处于失败状态  (默认 false )\n\t * @example <u-steps current=\"0\"><u-steps-item title=\"已出库\" desc=\"10:35\" ></u-steps-item></u-steps>\n\t */\n\texport default {\n\t\tname: 'u-steps-item',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tindex: 0,\n\t\t\t\tchildLength: 0,\n\t\t\t\tshowLine: false,\n\t\t\t\tsize: {\n\t\t\t\t\theight: 0,\n\t\t\t\t\twidth: 0\n\t\t\t\t},\n\t\t\t\tparentData: {\n\t\t\t\t\tdirection: 'row',\n\t\t\t\t\tcurrent: 0,\n\t\t\t\t\tactiveColor: '',\n\t\t\t\t\tinactiveColor: '',\n\t\t\t\t\tactiveIcon: '',\n\t\t\t\t\tinactiveIcon: '',\n\t\t\t\t\tdot: false\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t'parentData'(newValue, oldValue) {\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.init()\n\t\t},\n\t\tcomputed: {\n\t\t\tlineStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tif (this.parentData.direction === 'row') {\n\t\t\t\t\tstyle.width = this.size.width + 'px'\n\t\t\t\t\tstyle.left = this.size.width / 2 + 'px'\n\t\t\t\t} else {\n\t\t\t\t\tstyle.height = this.size.height + 'px'\n\t\t\t\t\t// style.top = this.size.height / 2 + 'px'\n\t\t\t\t}\n\t\t\t\tstyle.backgroundColor = this.parent.children?.[this.index + 1]?.error ? uni.$u.color.error : this.index <\n\t\t\t\t\tthis\n\t\t\t\t\t.parentData\n\t\t\t\t\t.current ? this.parentData.activeColor : this.parentData.inactiveColor\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tstatusClass() {\n\t\t\t\tconst {\n\t\t\t\t\tindex,\n\t\t\t\t\terror\n\t\t\t\t} = this\n\t\t\t\tconst {\n\t\t\t\t\tcurrent\n\t\t\t\t} = this.parentData\n\t\t\t\tif (current == index) {\n\t\t\t\t\treturn error === true ? 'error' : 'process'\n\t\t\t\t} else if (error) {\n\t\t\t\t\treturn 'error'\n\t\t\t\t} else if (current > index) {\n\t\t\t\t\treturn 'finish'\n\t\t\t\t} else {\n\t\t\t\t\treturn 'wait'\n\t\t\t\t}\n\t\t\t},\n\t\t\tstatusColor() {\n\t\t\t\tlet color = ''\n\t\t\t\tswitch (this.statusClass) {\n\t\t\t\t\tcase 'finish':\n\t\t\t\t\t\tcolor = this.parentData.activeColor\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'error':\n\t\t\t\t\t\tcolor = uni.$u.color.error\n\t\t\t\t\t\tbreak\n\t\t\t\t\tcase 'process':\n\t\t\t\t\t\tcolor = this.parentData.dot ? this.parentData.activeColor : 'transparent'\n\t\t\t\t\t\tbreak\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcolor = this.parentData.inactiveColor\n\t\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn color\n\t\t\t},\n\t\t\tcontentStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tif (this.parentData.direction === 'column') {\n\t\t\t\t\tstyle.marginLeft = this.parentData.dot ? '2px' : '6px'\n\t\t\t\t\tstyle.marginTop = this.parentData.dot ? '0px' : '6px'\n\t\t\t\t} else {\n\t\t\t\t\tstyle.marginTop = this.parentData.dot ? '2px' : '6px'\n\t\t\t\t\tstyle.marginLeft = this.parentData.dot ? '2px' : '6px'\n\t\t\t\t}\n\n\t\t\t\treturn style\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.parent && this.parent.updateFromChild()\n\t\t\tuni.$u.sleep().then(() => {\n\t\t\t\tthis.getStepsItemRect()\n\t\t\t})\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 初始化数据\n\t\t\t\tthis.updateParentData()\n\t\t\t\tif (!this.parent) {\n\t\t\t\t\treturn uni.$u.error('u-steps-item必须要搭配u-steps组件使用')\n\t\t\t\t}\n\t\t\t\tthis.index = this.parent.children.indexOf(this)\n\t\t\t\tthis.childLength = this.parent.children.length\n\t\t\t},\n\t\t\tupdateParentData() {\n\t\t\t\t// 此方法在mixin中\n\t\t\t\tthis.getParentData('u-steps')\n\t\t\t},\n\t\t\t// 父组件数据发生变化\n\t\t\tupdateFromParent() {\n\t\t\t\tthis.init()\n\t\t\t},\n\t\t\t// 获取组件的尺寸，用于设置横线的位置\n\t\t\tgetStepsItemRect() {\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.$uGetRect('.u-steps-item').then(size => {\n\t\t\t\t\tthis.size = size\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tdom.getComponentRect(this.$refs['u-steps-item'], res => {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tsize\n\t\t\t\t\t} = res\n\t\t\t\t\tthis.size = size\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-steps-item {\n\t\tflex: 1;\n\t\t@include flex;\n\n\t\t&--row {\n\t\t\tflex-direction: column;\n\t\t\talign-items: center;\n\t\t\tposition: relative;\n\t\t}\n\n\t\t&--column {\n\t\t\tposition: relative;\n\t\t\tflex-direction: row;\n\t\t\tjustify-content: flex-start;\n\t\t\tpadding-bottom: 5px;\n\t\t}\n\n\t\t&__wrapper {\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\tposition: relative;\n\t\t\tbackground-color: #fff;\n\n\t\t\t&--column {\n\t\t\t\twidth: 20px;\n\t\t\t\theight: 32px;\n\n\t\t\t\t&--dot {\n\t\t\t\t\theight: 20px;\n\t\t\t\t\twidth: 20px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&--row {\n\t\t\t\twidth: 32px;\n\t\t\t\theight: 20px;\n\n\t\t\t\t&--dot {\n\t\t\t\t\twidth: 20px;\n\t\t\t\t\theight: 20px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&__circle {\n\t\t\t\twidth: 20px;\n\t\t\t\theight: 20px;\n\t\t\t\t/* #ifndef APP-NVUE */\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\tflex-shrink: 0;\n\t\t\t\t/* #endif */\n\t\t\t\tborder-radius: 100px;\n\t\t\t\tborder-width: 1px;\n\t\t\t\tborder-color: $u-tips-color;\n\t\t\t\tborder-style: solid;\n\t\t\t\t@include flex(row);\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\ttransition: background-color 0.3s;\n\n\t\t\t\t&__text {\n\t\t\t\t\tcolor: $u-tips-color;\n\t\t\t\t\tfont-size: 11px;\n\t\t\t\t\t@include flex(row);\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t\tline-height: 11px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&__dot {\n\t\t\t\twidth: 10px;\n\t\t\t\theight: 10px;\n\t\t\t\tborder-radius: 100px;\n\t\t\t\tbackground-color: $u-content-color;\n\t\t\t}\n\t\t}\n\n\t\t&__content {\n\t\t\t@include flex;\n\t\t\tflex: 1;\n\n\t\t\t&--row {\n\t\t\t\tflex-direction: column;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\t&--column {\n\t\t\t\tflex-direction: column;\n\t\t\t\tmargin-left: 6px;\n\t\t\t}\n\t\t}\n\n\t\t&__line {\n\t\t\tposition: absolute;\n\t\t\tbackground: $u-tips-color;\n\n\t\t\t&--row {\n\t\t\t\ttop: 10px;\n\t\t\t\theight: 1px;\n\t\t\t}\n\n\t\t\t&--column {\n\t\t\t\twidth: 1px;\n\t\t\t\tleft: 10px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-sticky/props.js",
    "content": "export default {\n    props: {\n        // 吸顶容器到顶部某个距离的时候，进行吸顶，在H5平台，NavigationBar为44px\n        offsetTop: {\n            type: [String, Number],\n            default: uni.$u.props.sticky.offsetTop\n        },\n        // 自定义导航栏的高度\n        customNavHeight: {\n            type: [String, Number],\n            // #ifdef H5\n            // H5端的导航栏属于“自定义”导航栏的范畴，因为它是非原生的，与普通元素一致\n            default: 44,\n            // #endif\n            // #ifndef H5\n            default: uni.$u.props.sticky.customNavHeight\n            // #endif\n        },\n        // 是否开启吸顶功能\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.sticky.disabled\n        },\n        // 吸顶区域的背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.sticky.bgColor\n        },\n        // z-index值\n        zIndex: {\n            type: [String, Number],\n            default: uni.$u.props.sticky.zIndex\n        },\n        // 列表中的索引值\n        index: {\n            type: [String, Number],\n            default: uni.$u.props.sticky.index\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-sticky/u-sticky.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-sticky\"\n\t\t:id=\"elId\"\n\t\t:style=\"[style]\"\n\t>\n\t\t<view\n\t\t\t:style=\"[stickyContent]\"\n\t\t\tclass=\"u-sticky__content\"\n\t\t>\n\t\t\t<slot />\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';;\n\t/**\n\t * sticky 吸顶\n\t * @description 该组件与CSS中position: sticky属性实现的效果一致，当组件达到预设的到顶部距离时， 就会固定在指定位置，组件位置大于预设的顶部距离时，会重新按照正常的布局排列。\n\t * @tutorial https://www.uviewui.com/components/sticky.html\n\t * @property {String ｜ Number}\toffsetTop\t\t吸顶时与顶部的距离，单位px（默认 0 ）\n\t * @property {String ｜ Number}\tcustomNavHeight\t自定义导航栏的高度 （h5 默认44  其他默认 0 ）\n\t * @property {Boolean}\t\t\tdisabled\t\t是否开启吸顶功能 （默认 false ）\n\t * @property {String}\t\t\tbgColor\t\t\t组件背景颜色（默认 '#ffffff' ）\n\t * @property {String ｜ Number}\tzIndex\t\t\t吸顶时的z-index值\n\t * @property {String ｜ Number}\tindex\t\t\t自定义标识，用于区分是哪一个组件\n\t * @property {Object}\t\t\tcustomStyle\t\t组件的样式，对象形式\n\t * @event {Function} fixed\t\t组件吸顶时触发\n\t * @event {Function} unfixed\t组件取消吸顶时触发\n\t * @example <u-sticky offsetTop=\"200\"><view>塞下秋来风景异，衡阳雁去无留意</view></u-sticky>\n\t */\n\texport default {\n\t\tname: 'u-sticky',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tcssSticky: false, // 是否使用css的sticky实现\n\t\t\t\tstickyTop: 0, // 吸顶的top值，因为可能受自定义导航栏影响，最终的吸顶值非offsetTop值\n\t\t\t\telId: uni.$u.guid(),\n\t\t\t\tleft: 0, // js模式时，吸顶的内容因为处于postition: fixed模式，为了和原来保持一致的样式，需要记录并重新设置它的left，height，width属性\n\t\t\t\twidth: 'auto',\n\t\t\t\theight: 'auto',\n\t\t\t\tfixed: false, // js模式时，是否处于吸顶模式\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tstyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tif(!this.disabled) {\n\t\t\t\t\tif (this.cssSticky) {\n\t\t\t\t\t\tstyle.position = 'sticky'\n\t\t\t\t\t\tstyle.zIndex = this.uZindex\n\t\t\t\t\t\tstyle.top = uni.$u.addUnit(this.stickyTop)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstyle.height = this.fixed ? this.height + 'px' : 'auto'\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// 无需吸顶时，设置会默认的relative(nvue)和非nvue的static静态模式即可\n\t\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t\tstyle.position = 'relative'\n\t\t\t\t\t// #endif\n\t\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t\tstyle.position = 'static'\n\t\t\t\t\t// #endif\n\t\t\t\t}\n\t\t\t\tstyle.backgroundColor = this.bgColor\n\t\t\t\treturn uni.$u.deepMerge(uni.$u.addStyle(this.customStyle), style)\n\t\t\t},\n\t\t\t// 吸顶内容的样式\n\t\t\tstickyContent() {\n\t\t\t\tconst style = {}\n\t\t\t\tif (!this.cssSticky) {\n\t\t\t\t\tstyle.position = this.fixed ? 'fixed' : 'static'\n\t\t\t\t\tstyle.top = this.stickyTop + 'px'\n\t\t\t\t\tstyle.left = this.left + 'px'\n\t\t\t\t\tstyle.width = this.width == 'auto' ? 'auto' : this.width + 'px'\n\t\t\t\t\tstyle.zIndex = this.uZindex\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tuZindex() {\n\t\t\t\treturn this.zIndex ? this.zIndex : uni.$u.zIndex.sticky\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tthis.getStickyTop()\n\t\t\t\t// 判断使用的模式\n\t\t\t\tthis.checkSupportCssSticky()\n\t\t\t\t// 如果不支持css sticky，则使用js方案，此方案性能比不上css方案\n\t\t\t\tif (!this.cssSticky) {\n\t\t\t\t\t!this.disabled && this.initObserveContent()\n\t\t\t\t}\n\t\t\t},\n\t\t\tinitObserveContent() {\n\t\t\t\t// 获取吸顶内容的高度，用于在js吸顶模式时，给父元素一个填充高度，防止\"塌陷\"\n\t\t\t\tthis.$uGetRect('#' + this.elId).then((res) => {\n\t\t\t\t\tthis.height = res.height\n\t\t\t\t\tthis.left = res.left\n\t\t\t\t\tthis.width = res.width\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.observeContent()\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t\tobserveContent() {\n\t\t\t\t// 先断掉之前的观察\n\t\t\t\tthis.disconnectObserver('contentObserver')\n\t\t\t\tconst contentObserver = uni.createIntersectionObserver({\n\t\t\t\t\t// 检测的区间范围\n\t\t\t\t\tthresholds: [0.95, 0.98, 1]\n\t\t\t\t})\n\t\t\t\t// 到屏幕顶部的高度时触发\n\t\t\t\tcontentObserver.relativeToViewport({\n\t\t\t\t\ttop: -this.stickyTop\n\t\t\t\t})\n\t\t\t\t// 绑定观察的元素\n\t\t\t\tcontentObserver.observe(`#${this.elId}`, res => {\n\t\t\t\t\tthis.setFixed(res.boundingClientRect.top)\n\t\t\t\t})\n\t\t\t\tthis.contentObserver = contentObserver\n\t\t\t},\n\t\t\tsetFixed(top) {\n\t\t\t\t// 判断是否出于吸顶条件范围\n\t\t\t\tconst fixed = top <= this.stickyTop\n\t\t\t\tthis.fixed = fixed\n\t\t\t},\n\t\t\tdisconnectObserver(observerName) {\n\t\t\t\t// 断掉观察，释放资源\n\t\t\t\tconst observer = this[observerName]\n\t\t\t\tobserver && observer.disconnect()\n\t\t\t},\n\t\t\tgetStickyTop() {\n\t\t\t\tthis.stickyTop = uni.$u.getPx(this.offsetTop) + uni.$u.getPx(this.customNavHeight)\n\t\t\t},\n\t\t\tasync checkSupportCssSticky() {\n\t\t\t\t// #ifdef H5\n\t\t\t\t// H5，一般都是现代浏览器，是支持css sticky的，这里使用创建元素嗅探的形式判断\n\t\t\t\tif (this.checkCssStickyForH5()) {\n\t\t\t\t\tthis.cssSticky = true\n\t\t\t\t}\n\t\t\t\t// #endif\n\n\t\t\t\t// 如果安卓版本高于8.0，依然认为是支持css sticky的(因为安卓7在某些机型，可能不支持sticky)\n\t\t\t\tif (uni.$u.os() === 'android' && Number(uni.$u.sys().system) > 8) {\n\t\t\t\t\tthis.cssSticky = true\n\t\t\t\t}\n\n\t\t\t\t// APP-Vue和微信平台，通过computedStyle判断是否支持css sticky\n\t\t\t\t// #ifdef APP-VUE || MP-WEIXIN\n\t\t\t\tthis.cssSticky = await this.checkComputedStyle()\n\t\t\t\t// #endif\n\n\t\t\t\t// ios上，从ios6开始，都是支持css sticky的\n\t\t\t\tif (uni.$u.os() === 'ios') {\n\t\t\t\t\tthis.cssSticky = true\n\t\t\t\t}\n\n\t\t\t\t// nvue，是支持css sticky的\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tthis.cssSticky = true\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 在APP和微信小程序上，通过uni.createSelectorQuery可以判断是否支持css sticky\n\t\t\tcheckComputedStyle() {\n\t\t\t\t// 方法内进行判断，避免在其他平台生成无用代码\n\t\t\t\t// #ifdef APP-VUE || MP-WEIXIN\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tuni.createSelectorQuery().in(this).select('.u-sticky').fields({\n\t\t\t\t\t\tcomputedStyle: [\"position\"]\n\t\t\t\t\t}).exec(e => {\n\t\t\t\t\t\tresolve('sticky' === e[0].position)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// H5通过创建元素的形式嗅探是否支持css sticky\n\t\t\t// 判断浏览器是否支持sticky属性\n\t\t\tcheckCssStickyForH5() {\n\t\t\t\t// 方法内进行判断，避免在其他平台生成无用代码\n\t\t\t\t// #ifdef H5\n\t\t\t\tconst vendorList = ['', '-webkit-', '-ms-', '-moz-', '-o-'],\n\t\t\t\t\tvendorListLength = vendorList.length,\n\t\t\t\t\tstickyElement = document.createElement('div')\n\t\t\t\tfor (let i = 0; i < vendorListLength; i++) {\n\t\t\t\t\tstickyElement.style.position = vendorList[i] + 'sticky'\n\t\t\t\t\tif (stickyElement.style.position !== '') {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t\t// #endif\n\t\t\t}\n\t\t},\n\t\tbeforeDestroy() {\n\t\t\tthis.disconnectObserver('contentObserver')\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t.u-sticky {\n\t\t/* #ifdef APP-VUE || MP-WEIXIN */\n\t\t// 此处默认写sticky属性，是为了给微信和APP通过uni.createSelectorQuery查询是否支持css sticky使用\n\t\tposition: sticky;\n\t\t/* #endif */\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-subsection/props.js",
    "content": "export default {\n    props: {\n        // tab的数据\n        list: {\n            type: Array,\n            default: uni.$u.props.subsection.list\n        },\n        // 当前活动的tab的index\n        current: {\n            type: [String, Number],\n            default: uni.$u.props.subsection.current\n        },\n        // 激活的颜色\n        activeColor: {\n            type: String,\n            default: uni.$u.props.subsection.activeColor\n        },\n        // 未激活的颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.subsection.inactiveColor\n        },\n        // 模式选择，mode=button为按钮形式，mode=subsection时为分段模式\n        mode: {\n            type: String,\n            default: uni.$u.props.subsection.mode\n        },\n        // 字体大小\n        fontSize: {\n            type: [String, Number],\n            default: uni.$u.props.subsection.fontSize\n        },\n        // 激活tab的字体是否加粗\n        bold: {\n            type: Boolean,\n            default: uni.$u.props.subsection.bold\n        },\n        // mode = button时，组件背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.subsection.bgColor\n        },\n\t\t// 从list元素对象中读取的键名\n\t\tkeyName: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.subsection.keyName\n\t\t}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-subsection/u-subsection.vue",
    "content": "<template>\n    <view\n        class=\"u-subsection\"\n        ref=\"u-subsection\"\n        :class=\"[`u-subsection--${mode}`]\"\n        :style=\"[$u.addStyle(customStyle), wrapperStyle]\"\n    >\n        <view\n            class=\"u-subsection__bar\"\n            ref=\"u-subsection__bar\"\n            :style=\"[barStyle]\"\n            :class=\"[\n                mode === 'button' && 'u-subsection--button__bar',\n                current === 0 &&\n                    mode === 'subsection' &&\n                    'u-subsection__bar--first',\n                current > 0 &&\n                    current < list.length - 1 &&\n                    mode === 'subsection' &&\n                    'u-subsection__bar--center',\n                current === list.length - 1 &&\n                    mode === 'subsection' &&\n                    'u-subsection__bar--last',\n            ]\"\n        ></view>\n        <view\n            class=\"u-subsection__item\"\n            :class=\"[\n                `u-subsection__item--${index}`,\n                index < list.length - 1 &&\n                    'u-subsection__item--no-border-right',\n                index === 0 && 'u-subsection__item--first',\n                index === list.length - 1 && 'u-subsection__item--last',\n            ]\"\n            :ref=\"`u-subsection__item--${index}`\"\n            :style=\"[itemStyle(index)]\"\n            @tap=\"clickHandler(index)\"\n            v-for=\"(item, index) in list\"\n            :key=\"index\"\n        >\n            <text\n                class=\"u-subsection__item__text\"\n                :style=\"[textStyle(index)]\"\n                >{{ getText(item) }}</text\n            >\n        </view>\n    </view>\n</template>\n\n<script>\n// #ifdef APP-NVUE\nconst dom = uni.requireNativePlugin(\"dom\");\nconst animation = uni.requireNativePlugin(\"animation\");\n// #endif\nimport props from \"./props.js\";\n/**\n * Subsection 分段器\n * @description 该分段器一般用于用户从几个选项中选择某一个的场景\n * @tutorial https://www.uviewui.com/components/subsection.html\n * @property {Array}\t\t\tlist\t\t\ttab的数据\n * @property {String ｜ Number}\tcurrent\t\t\t当前活动的tab的index（默认 0 ）\n * @property {String}\t\t\tactiveColor\t\t激活时的颜色（默认 '#3c9cff' ）\n * @property {String}\t\t\tinactiveColor\t未激活时的颜色（默认 '#303133' ）\n * @property {String}\t\t\tmode\t\t\t模式选择，mode=button为按钮形式，mode=subsection时为分段模式（默认 'button' ）\n * @property {String ｜ Number}\tfontSize\t\t字体大小，单位px（默认 12 ）\n * @property {Boolean}\t\t\tbold\t\t\t激活选项的字体是否加粗（默认 true ）\n * @property {String}\t\t\tbgColor\t\t\t组件背景颜色，mode为button时有效（默认 '#eeeeef' ）\n * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\n * @property {String}\t        keyName\t        从`list`元素对象中读取的键名（默认 'name' ）\n *\n * @event {Function} change\t\t分段器选项发生改变时触发  回调 index：选项的index索引值，从0开始\n * @example <u-subsection :list=\"list\" :current=\"curNow\" @change=\"sectionChange\"></u-subsection>\n */\nexport default {\n    name: \"u-subsection\",\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n    data() {\n        return {\n            // 组件尺寸\n            itemRect: {\n                width: 0,\n                height: 0,\n            },\n        };\n    },\n    watch: {\n        list(newValue, oldValue) {\n            this.init();\n        },\n        current: {\n            immediate: true,\n            handler(n) {\n                // #ifdef APP-NVUE\n                // 在安卓nvue上，如果通过translateX进行位移，到最后一个时，会导致右侧无法绘制圆角\n                // 故用animation模块进行位移\n                const ref = this.$refs?.[\"u-subsection__bar\"]?.ref;\n                // 不存在ref的时候(理解为第一次初始化时，需要渲染dom，进行一定延时再获取ref)，这里的100ms是经过测试得出的结果(某些安卓需要延时久一点)，勿随意修改\n                uni.$u.sleep(ref ? 0 : 100).then(() => {\n                    animation.transition(this.$refs[\"u-subsection__bar\"].ref, {\n                        styles: {\n                            transform: `translateX(${\n                                n * this.itemRect.width\n                            }px)`,\n                            transformOrigin: \"center center\",\n                        },\n                        duration: 300,\n                    });\n                });\n                // #endif\n            },\n        },\n    },\n    computed: {\n        wrapperStyle() {\n            const style = {};\n            // button模式时，设置背景色\n            if (this.mode === \"button\") {\n                style.backgroundColor = this.bgColor;\n            }\n            return style;\n        },\n        // 滑块的样式\n        barStyle() {\n            const style = {};\n            style.width = `${this.itemRect.width}px`;\n            style.height = `${this.itemRect.height}px`;\n            // 通过translateX移动滑块，其移动的距离为索引*item的宽度\n            // #ifndef APP-NVUE\n            style.transform = `translateX(${\n                this.current * this.itemRect.width\n            }px)`;\n            // #endif\n            if (this.mode === \"subsection\") {\n                // 在subsection模式下，需要动态设置滑块的圆角，因为移动滑块使用的是translateX，无法通过父元素设置overflow: hidden隐藏滑块的直角\n                style.backgroundColor = this.activeColor;\n            }\n            return style;\n        },\n        // 分段器item的样式\n        itemStyle(index) {\n            return (index) => {\n                const style = {};\n                if (this.mode === \"subsection\") {\n                    // 设置border的样式\n                    style.borderColor = this.activeColor;\n                    style.borderWidth = \"1px\";\n                    style.borderStyle = \"solid\";\n                }\n                return style;\n            };\n        },\n        // 分段器文字颜色\n        textStyle(index) {\n            return (index) => {\n                const style = {};\n                style.fontWeight =\n                    this.bold && this.current === index ? \"bold\" : \"normal\";\n                style.fontSize = uni.$u.addUnit(this.fontSize);\n                // subsection模式下，激活时默认为白色的文字\n                if (this.mode === \"subsection\") {\n                    style.color =\n                        this.current === index ? \"#fff\" : this.inactiveColor;\n                } else {\n                    // button模式下，激活时文字颜色默认为activeColor\n                    style.color =\n                        this.current === index\n                            ? this.activeColor\n                            : this.inactiveColor;\n                }\n                return style;\n            };\n        },\n    },\n    mounted() {\n        this.init();\n    },\n    methods: {\n        init() {\n            uni.$u.sleep().then(() => this.getRect());\n        },\n\t\t// 判断展示文本\n\t\tgetText(item) {\n\t\t\treturn typeof item === 'object' ? item[this.keyName] : item\n\t\t},\n        // 获取组件的尺寸\n        getRect() {\n            // #ifndef APP-NVUE\n            this.$uGetRect(\".u-subsection__item--0\").then((size) => {\n                this.itemRect = size;\n            });\n            // #endif\n\n            // #ifdef APP-NVUE\n            const ref = this.$refs[\"u-subsection__item--0\"][0];\n            ref &&\n                dom.getComponentRect(ref, (res) => {\n                    this.itemRect = res.size;\n                });\n            // #endif\n        },\n        clickHandler(index) {\n            this.$emit(\"change\", index);\n        },\n    },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n\n.u-subsection {\n    @include flex;\n    position: relative;\n    overflow: hidden;\n\t/* #ifndef APP-NVUE */\n\twidth: 100%;\n\tbox-sizing: border-box;\n\t/* #endif */\n\n    &--button {\n        height: 32px;\n        background-color: rgb(238, 238, 239);\n        padding: 3px;\n        border-radius: 3px;\n        align-items: stretch;\n\n        &__bar {\n            background-color: #ffffff;\n            border-radius: 3px !important;\n        }\n    }\n\n    &--subsection {\n        height: 30px;\n    }\n\n    &__bar {\n        position: absolute;\n        /* #ifndef APP-NVUE */\n        transition-property: transform, color;\n        transition-duration: 0.3s;\n        transition-timing-function: ease-in-out;\n        /* #endif */\n\n        &--first {\n            border-top-left-radius: 3px;\n            border-bottom-left-radius: 3px;\n            border-top-right-radius: 0px;\n            border-bottom-right-radius: 0px;\n        }\n\n        &--center {\n            border-top-left-radius: 0px;\n            border-bottom-left-radius: 0px;\n            border-top-right-radius: 0px;\n            border-bottom-right-radius: 0px;\n        }\n\n        &--last {\n            border-top-left-radius: 0px;\n            border-bottom-left-radius: 0px;\n            border-top-right-radius: 3px;\n            border-bottom-right-radius: 3px;\n        }\n    }\n\n    &__item {\n        @include flex;\n        flex: 1;\n        justify-content: center;\n        align-items: center;\n        // vue环境下，需要设置相对定位，因为滑块为绝对定位，item需要在滑块的上面\n        position: relative;\n\n        &--no-border-right {\n            border-right-width: 0 !important;\n        }\n\n        &--first {\n            border-top-left-radius: 3px;\n            border-bottom-left-radius: 3px;\n        }\n\n        &--last {\n            border-top-right-radius: 3px;\n            border-bottom-right-radius: 3px;\n        }\n\n        &__text {\n            font-size: 12px;\n            line-height: 12px;\n            @include flex;\n            align-items: center;\n            transition-property: color;\n            transition-duration: 0.3s;\n        }\n    }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action/props.js",
    "content": "export default {\n    props: {\n        // 是否自动关闭其他swipe按钮组\n        autoClose: {\n            type: Boolean,\n            default: uni.$u.props.swipeAction.autoClose\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action/u-swipe-action.vue",
    "content": "<template>\n\t<view class=\"u-swipe-action\">\n\t\t<slot></slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * SwipeAction 滑动单元格 \n\t * @description 该组件一般用于左滑唤出操作菜单的场景，用的最多的是左滑删除操作\n\t * @tutorial https://www.uviewui.com/components/swipeAction.html\n\t * @property {Boolean}\tautoClose\t是否自动关闭其他swipe按钮组\n\t * @event {Function(index)}\tclick\t点击组件时触发\n\t * @example\t<u-swipe-action><u-swipe-action-item :rightOptions=\"options1\" ></u-swipe-action-item></u-swipe-action>\n\t */\n\texport default {\n\t\tname: 'u-swipe-action',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {}\n\t\t},\n\t\tprovide() {\n\t\t\treturn {\n\t\t\t\tswipeAction: this\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 这里computed的变量，都是子组件u-swipe-action-item需要用到的，由于头条小程序的兼容性差异，子组件无法实时监听父组件参数的变化\n\t\t\t// 所以需要手动通知子组件，这里返回一个parentData变量，供watch监听，在其中去通知每一个子组件重新从父组件(u-swipe-action-item)\n\t\t\t// 拉取父组件新的变化后的参数\n\t\t\tparentData() {\n\t\t\t\treturn [this.autoClose]\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\n\t\t\tparentData() {\n\t\t\t\tif (this.children.length) {\n\t\t\t\t\tthis.children.map(child => {\n\t\t\t\t\t\t// 判断子组件(u-swipe-action-item)如果有updateParentData方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\n\t\t\t\t\t\ttypeof(child.updateParentData) === 'function' && child.updateParentData()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tcreated() {\n\t\t\tthis.children = []\n\t\t},\n\t\tmethods: {\n\t\t\tcloseOther(child) {\n\t\t\t\tif (this.autoClose) {\n\t\t\t\t\t// 历遍所有的单元格，找出非当前操作中的单元格，进行关闭\n\t\t\t\t\tthis.children.map((item, index) => {\n\t\t\t\t\t\tif (child !== item) {\n\t\t\t\t\t\t\titem.closeHandler()\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</script>\n\n<style lang=\"scss\" scoped>\n\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action-item/index - backup.wxs",
    "content": "/**\n * 此为wxs模块，只支持APP-VUE，微信和QQ小程序以及H5平台\n * wxs内部不支持es6语法，变量只能使用var定义，无法使用解构，箭头函数等特性\n */\n\n// 开始触摸\nfunction touchstart(event, ownerInstance) {\n\t// 触发事件的组件的ComponentDescriptor实例\n\tvar instance = event.instance\n\t// wxs内的局部变量快照，此快照是属于整个组件的，在touchstart和touchmove事件中都能获取到相同的结果\n\tvar state = instance.getState()\n\tif (state.disable) return\n\tvar touches = event.touches\n\t// 如果进行的是多指触控，不允许进行操作\n\tif (touches && touches.length > 1) return\n\t// 标识当前为滑动中状态\n\tstate.moving = true\n\t// 记录触摸开始点的坐标值\n\tstate.startX = touches[0].pageX\n\tstate.startY = touches[0].pageY\n}\n\n// 触摸滑动\nfunction touchmove(event, ownerInstance) {\n\t// 触发事件的组件的ComponentDescriptor实例\n\tvar instance = event.instance\n\t// wxs内的局部变量快照\n\tvar state = instance.getState()\n\tif (state.disabled || !state.moving) return\n\n\tvar touches = event.touches\n\tvar pageX = touches[0].pageX\n\tvar pageY = touches[0].pageY\n\tvar moveX = pageX - state.startX\n\tvar moveY = pageY - state.startY\n\tvar buttonsWidth = state.buttonsWidth\n\n\t// 移动的X轴距离大于Y轴距离，也即终点与起点位置连线，与X轴夹角小于45度时，禁止页面滚动\n\tif (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {\n\t\tevent.preventDefault()\n\t\tevent.stopPropagation()\n\t}\n\t// 如果移动的X轴距离小于Y轴距离，也即终点位置与起点位置连线，与Y轴夹角小于45度时，认为是页面上下滑动，而不是左右滑动单元格\n\tif (Math.abs(moveX) < Math.abs(moveY)) return\n\n\t// 限制右滑的距离，不允许内容部分往右偏移，右滑会导致X轴偏移值大于0，以此做判断\n\t// 此处不能直接return，因为滑动过程中会缺失某些关键点坐标，会导致错乱，最好的办法就是\n\t// 在超出后，设置为0\n\tif (state.status === 'open') {\n\t\t// 在开启状态下，向左滑动，需忽略\n\t\tif (moveX < 0) moveX = 0\n\t\t// 想要收起菜单，最大能移动的距离为按钮的总宽度\n\t\tif (moveX > buttonsWidth) moveX = buttonsWidth\n\t\t// 如果是已经打开了的状态，向左滑动时，移动收起菜单\n\t\tmoveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)\n\t} else {\n\t\t// 关闭状态下，右滑动需忽略\n\t\tif (moveX > 0) moveX = 0\n\t\t// 滑动的距离不允许超过所有按钮的总宽度，此时只能是左滑，最终设置按钮的总宽度，同时为负数\n\t\tif (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth\n\t\t// 只要是在滑过程中，就不断移动菜单的内容部分，从而使隐藏的菜单显示出来\n\t\tmoveSwipeAction(moveX, instance, ownerInstance)\n\t}\n}\n\n// 触摸结束\nfunction touchend(event, ownerInstance) {\n\t// 触发事件的组件的ComponentDescriptor实例\n\tvar instance = event.instance\n\t// wxs内的局部变量快照\n\tvar state = instance.getState()\n\tif (!state.moving || state.disabled) return\n\tvar touches = event.changedTouches ? event.changedTouches[0] : {}\n\tvar pageX = touches.pageX\n\tvar pageY = touches.pageY\n\tvar moveX = pageX - state.startX\n\tif (state.status === 'open') {\n\t\t// 在展开的状态下，继续左滑，无需操作\n\t\tif (moveX < 0) return\n\t\t// 在开启状态下，点击一下内容区域，moveX为0，也即没有进行移动，这时执行收起菜单逻辑\n\t\tif (moveX === 0) {\n\t\t\treturn closeSwipeAction(instance, ownerInstance)\n\t\t}\n\t\t// 在开启状态下，滑动距离小于阈值，则默认为不关闭，同时恢复原来的打开状态\n\t\tif (Math.abs(moveX) < state.threshold) {\n\t\t\topenSwipeAction(instance, ownerInstance)\n\t\t} else {\n\t\t\t// 如果滑动距离大于阈值，则执行收起逻辑\n\t\t\tcloseSwipeAction(instance, ownerInstance)\n\t\t}\n\t} else {\n\t\t// 在关闭的状态下，右滑，无需操作\n\t\tif (moveX > 0) return\n\t\t// 理由同上\n\t\tif (Math.abs(moveX) < state.threshold) {\n\t\t\tcloseSwipeAction(instance, ownerInstance)\n\t\t} else {\n\t\t\topenSwipeAction(instance, ownerInstance)\n\t\t}\n\t}\n}\n\n// 获取过渡时间\nfunction getDuration(value) {\n\tif (value.toString().indexOf('s') >= 0) return value\n\treturn value > 30 ? value + 'ms' : value + 's'\n}\n\n// 滑动结束时判断滑动的方向\nfunction getMoveDirection(instance, ownerInstance) {\n\tvar state = instance.getState()\n}\n\n// 移动滑动选择器内容区域，同时显示出其隐藏的菜单\nfunction moveSwipeAction(moveX, instance, ownerInstance) {\n\tvar state = instance.getState()\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\n\tvar buttons = ownerInstance.selectAllComponents('.u-swipe-action-item__right__button')\n\tvar len = buttons.length\n\tvar previewButtonsMoveX = 0\n\n\t// 设置菜单内容部分的偏移\n\tinstance.requestAnimationFrame(function() {\n\t\tinstance.setStyle({\n\t\t\t// 设置translateX的值\n\t\t\t'transition': 'none',\n\t\t\ttransform: 'translateX(' + moveX + 'px)',\n\t\t\t'-webkit-transform': 'translateX(' + moveX + 'px)'\n\t\t})\n\t\t// 折叠按钮动画\n\t\tfor (var i = len - 1; i >= 0; i--) {\n\t\t\t// 通过比例，得出元素自身该移动的距离\n\t\t\tvar translateX = state.buttons[i].width / state.buttonsWidth * moveX\n\t\t\t// 最终移动的距离，是通过自身比例算出的距离，再加上在它之前所有按钮移动的距离之和\n\t\t\tvar realTranslateX = translateX + previewButtonsMoveX\n\t\t\tbuttons[i].setStyle({\n\t\t\t\t// 在移动期间，不能使用过渡效果，否则会造成卡顿，本质原因是每次移动一点，就要花一定时间去过渡这个过程\n\t\t\t\t'transition': 'none',\n\t\t\t\t'transform': 'translateX(' + realTranslateX + 'px)',\n\t\t\t\t'-webkit-transform': 'translateX(' + realTranslateX + 'px)'\n\t\t\t})\n\t\t\t// 记录本按钮之前的所有按钮的移动距离之和\n\t\t\tpreviewButtonsMoveX += translateX\n\t\t}\n\t})\n}\n\n// 一次性展开滑动菜单\nfunction openSwipeAction(instance, ownerInstance) {\n\tvar state = instance.getState()\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\n\tvar buttons = ownerInstance.selectAllComponents('.u-swipe-action-item__right__button')\n\tvar len = buttons.length\n\t// 处理duration单位问题\n\tconst duration = getDuration(state.duration)\n\t// 展开过程中，是向左移动，所以X的偏移应该为负值\n\tvar buttonsWidth = -state.buttonsWidth\n\tvar previewButtonsMoveX = 0\n\tinstance.requestAnimationFrame(function() {\n\t\t// 设置菜单主体内容\n\t\tinstance.setStyle({\n\t\t\t'transition': 'transform ' + duration,\n\t\t\t'transform': 'translateX(' + buttonsWidth + 'px)',\n\t\t\t'-webkit-transform': 'translateX(' + buttonsWidth + 'px)',\n\t\t})\n\t\t// 设置各个隐藏的按钮为展开的状态\n\t\tfor (var i = len - 1; i >= 0; i--) {\n\t\t\t// 通过比例，得出元素自身该移动的距离\n\t\t\tvar translateX = state.buttons[i].width / state.buttonsWidth * buttonsWidth\n\t\t\t// 最终移动的距离，是通过自身比例算出的距离，再加上在它之前所有按钮移动的距离之和\n\t\t\tvar realTranslateX = translateX + previewButtonsMoveX\n\t\t\tbuttons[i].setStyle({\n\t\t\t\t// 在移动期间，需要加上动画效果\n\t\t\t\t'transition': 'transform ' + duration,\n\t\t\t\t'transform': 'translateX(' + realTranslateX + 'px)',\n\t\t\t\t'-webkit-transform': 'translateX(' + realTranslateX + 'px)'\n\t\t\t})\n\t\t\t// 记录本按钮之前的所有按钮的移动距离之和\n\t\t\tpreviewButtonsMoveX += translateX\n\t\t}\n\t})\n\tsetStatus('open', instance)\n}\n\n// 标记菜单的当前状态，open-已经打开，close-已经关闭\nfunction setStatus(status, instance) {\n\tvar state = instance.getState()\n\tstate.status = status\n}\n\n// 一次性收起滑动菜单\nfunction closeSwipeAction(instance, ownerInstance) {\n\tvar state = instance.getState()\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\n\tvar buttons = ownerInstance.selectAllComponents('.u-swipe-action-item__right__button')\n\tvar len = buttons.length\n\t// 处理duration单位问题\n\tconst duration = getDuration(state.duration)\n\tinstance.requestAnimationFrame(function() {\n\t\t// 设置菜单主体内容\n\t\tinstance.setStyle({\n\t\t\t'transition': 'transform ' + duration,\n\t\t\t'transform': 'translateX(0px)',\n\t\t\t'-webkit-transform': 'translateX(0px)'\n\t\t})\n\t\t// 设置各个隐藏的按钮为收起的状态\n\t\tfor (var i = len - 1; i >= 0; i--) {\n\t\t\tbuttons[i].setStyle({\n\t\t\t\t'transition': 'transform ' + duration,\n\t\t\t\t'transform': 'translateX(0px)',\n\t\t\t\t'-webkit-transform': 'translateX(0px)'\n\t\t\t})\n\t\t}\n\t})\n\tsetStatus('close', instance)\n}\n\n// show的状态发生变化\nfunction showChange(newValue, oldValue, ownerInstance, instance) {\n\tvar state = instance.getState()\n\tif (state.disabled) return\n\t// 打开或关闭单元格\n\tif (newValue) {\n\t\topenSwipeAction(instance, ownerInstance)\n\t} else {\n\t\tcloseSwipeAction(instance, ownerInstance)\n\t}\n}\n\n// 菜单尺寸发生变化\nfunction sizeChange(newValue, oldValue, ownerInstance, instance) {\n\t// wxs内的局部变量快照\n\tvar state = instance.getState()\n\tstate.disabled = newValue.disabled\n\tstate.duration = newValue.duration\n\tstate.show = newValue.show\n\tstate.threshold = newValue.threshold\n\tstate.buttons = newValue.buttons\n\n\tvar len = state.buttons.length\n\tif (len) {\n\t\tvar buttonsWidth = 0\n\t\tvar buttons = newValue.buttons\n\t\tfor (var i = 0; i < len; i++) {\n\t\t\tbuttonsWidth += buttons[i].width\n\t\t}\n\t}\n\tstate.buttonsWidth = buttonsWidth\n}\n\nmodule.exports = {\n\ttouchstart: touchstart,\n\ttouchmove: touchmove,\n\ttouchend: touchend,\n\tsizeChange: sizeChange\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action-item/index.wxs",
    "content": "/**\n * 此为wxs模块，只支持APP-VUE，微信和QQ小程序以及H5平台\n * wxs内部不支持es6语法，变量只能使用var定义，无法使用解构，箭头函数等特性\n */\n\n// 开始触摸\nfunction touchstart(event, ownerInstance) {\n\t// 触发事件的组件的ComponentDescriptor实例\n\tvar instance = event.instance\n\t// wxs内的局部变量快照，此快照是属于整个组件的，在touchstart和touchmove事件中都能获取到相同的结果\n\tvar state = instance.getState()\n\tif (state.disabled) return\n\tvar touches = event.touches\n\t// 如果进行的是多指触控，不允许进行操作\n\tif (touches && touches.length > 1) return\n\t// 标识当前为滑动中状态\n\tstate.moving = true\n\t// 记录触摸开始点的坐标值\n\tstate.startX = touches[0].pageX\n\tstate.startY = touches[0].pageY\n\t\n\townerInstance.callMethod('closeOther')\n}\n\n// 触摸滑动\nfunction touchmove(event, ownerInstance) {\n\t// 触发事件的组件的ComponentDescriptor实例\n\tvar instance = event.instance\n\t// wxs内的局部变量快照\n\tvar state = instance.getState()\n\tif (state.disabled || !state.moving) return\n\tvar touches = event.touches\n\tvar pageX = touches[0].pageX\n\tvar pageY = touches[0].pageY\n\tvar moveX = pageX - state.startX\n\tvar moveY = pageY - state.startY\n\tvar buttonsWidth = state.buttonsWidth\n\n\t// 移动的X轴距离大于Y轴距离，也即终点与起点位置连线，与X轴夹角小于45度时，禁止页面滚动\n\tif (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {\n\t\tevent.preventDefault && event.preventDefault()\n\t\tevent.stopPropagation && event.stopPropagation()\n\t}\n\t// 如果移动的X轴距离小于Y轴距离，也即终点位置与起点位置连线，与Y轴夹角小于45度时，认为是页面上下滑动，而不是左右滑动单元格\n\tif (Math.abs(moveX) < Math.abs(moveY)) return\n\n\t// 限制右滑的距离，不允许内容部分往右偏移，右滑会导致X轴偏移值大于0，以此做判断\n\t// 此处不能直接return，因为滑动过程中会缺失某些关键点坐标，会导致错乱，最好的办法就是\n\t// 在超出后，设置为0\n\tif (state.status === 'open') {\n\t\t// 在开启状态下，向左滑动，需忽略\n\t\tif (moveX < 0) moveX = 0\n\t\t// 想要收起菜单，最大能移动的距离为按钮的总宽度\n\t\tif (moveX > buttonsWidth) moveX = buttonsWidth\n\t\t// 如果是已经打开了的状态，向左滑动时，移动收起菜单\n\t\tmoveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)\n\t} else {\n\t\t// 关闭状态下，右滑动需忽略\n\t\tif (moveX > 0) moveX = 0\n\t\t// 滑动的距离不允许超过所有按钮的总宽度，此时只能是左滑，最终设置按钮的总宽度，同时为负数\n\t\tif (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth\n\t\t// 只要是在滑过程中，就不断移动单元格内容部分，从而使隐藏的菜单显示出来\n\t\tmoveSwipeAction(moveX, instance, ownerInstance)\n\t}\n}\n\n// 触摸结束\nfunction touchend(event, ownerInstance) {\n\t// 触发事件的组件的ComponentDescriptor实例\n\tvar instance = event.instance\n\t// wxs内的局部变量快照\n\tvar state = instance.getState()\n\tif (!state.moving || state.disabled) return\n\tvar touches = event.changedTouches ? event.changedTouches[0] : {}\n\tvar pageX = touches.pageX\n\tvar pageY = touches.pageY\n\tvar moveX = pageX - state.startX\n\tif (state.status === 'open') {\n\t\t// 在展开的状态下，继续左滑，无需操作\n\t\tif (moveX < 0) return\n\t\t// 在开启状态下，点击一下内容区域，moveX为0，也即没有进行移动，这时执行收起菜单逻辑\n\t\tif (moveX === 0) {\n\t\t\treturn closeSwipeAction(instance, ownerInstance)\n\t\t}\n\t\t// 在开启状态下，滑动距离小于阈值，则默认为不关闭，同时恢复原来的打开状态\n\t\tif (Math.abs(moveX) < state.threshold) {\n\t\t\topenSwipeAction(instance, ownerInstance)\n\t\t} else {\n\t\t\t// 如果滑动距离大于阈值，则执行收起逻辑\n\t\t\tcloseSwipeAction(instance, ownerInstance)\n\t\t}\n\t} else {\n\t\t// 在关闭的状态下，右滑，无需操作\n\t\tif (moveX > 0) return\n\t\t// 理由同上\n\t\tif (Math.abs(moveX) < state.threshold) {\n\t\t\tcloseSwipeAction(instance, ownerInstance)\n\t\t} else {\n\t\t\topenSwipeAction(instance, ownerInstance)\n\t\t}\n\t}\n}\n\n// 获取过渡时间\nfunction getDuration(value) {\n\tif (value.toString().indexOf('s') >= 0) return value\n\treturn value > 30 ? value + 'ms' : value + 's'\n}\n\n// 滑动结束时判断滑动的方向\nfunction getMoveDirection(instance, ownerInstance) {\n\tvar state = instance.getState()\n}\n\n// 移动滑动选择器内容区域，同时显示出其隐藏的菜单\nfunction moveSwipeAction(moveX, instance, ownerInstance) {\n\tvar state = instance.getState()\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\n\tvar buttons = ownerInstance.selectAllComponents('.u-swipe-action-item__right__button')\n\n\t// 设置菜单内容部分的偏移\n\tinstance.requestAnimationFrame(function() {\n\t\tinstance.setStyle({\n\t\t\t// 设置translateX的值\n\t\t\t'transition': 'none',\n\t\t\ttransform: 'translateX(' + moveX + 'px)',\n\t\t\t'-webkit-transform': 'translateX(' + moveX + 'px)'\n\t\t})\n\t})\n}\n\n// 一次性展开滑动菜单\nfunction openSwipeAction(instance, ownerInstance) {\n\tvar state = instance.getState()\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\n\tvar buttons = ownerInstance.selectAllComponents('.u-swipe-action-item__right__button')\n\t// 处理duration单位问题\n\tvar duration = getDuration(state.duration)\n\t// 展开过程中，是向左移动，所以X的偏移应该为负值\n\tvar buttonsWidth = -state.buttonsWidth\n\tinstance.requestAnimationFrame(function() {\n\t\t// 设置菜单主体内容\n\t\tinstance.setStyle({\n\t\t\t'transition': 'transform ' + duration,\n\t\t\t'transform': 'translateX(' + buttonsWidth + 'px)',\n\t\t\t'-webkit-transform': 'translateX(' + buttonsWidth + 'px)',\n\t\t})\n\t})\n\tsetStatus('open', instance, ownerInstance)\n}\n\n// 标记菜单的当前状态，open-已经打开，close-已经关闭\nfunction setStatus(status, instance, ownerInstance) {\n\tvar state = instance.getState()\n\tstate.status = status\n\townerInstance.callMethod('setState', status)\n}\n\n// 一次性收起滑动菜单\nfunction closeSwipeAction(instance, ownerInstance) {\n\tvar state = instance.getState()\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\n\tvar buttons = ownerInstance.selectAllComponents('.u-swipe-action-item__right__button')\n\tvar len = buttons.length\n\t// 处理duration单位问题\n\tvar duration = getDuration(state.duration)\n\tinstance.requestAnimationFrame(function() {\n\t\t// 设置菜单主体内容\n\t\tinstance.setStyle({\n\t\t\t'transition': 'transform ' + duration,\n\t\t\t'transform': 'translateX(0px)',\n\t\t\t'-webkit-transform': 'translateX(0px)'\n\t\t})\n\t\t// 设置各个隐藏的按钮为收起的状态\n\t\tfor (var i = len - 1; i >= 0; i--) {\n\t\t\tbuttons[i].setStyle({\n\t\t\t\t'transition': 'transform ' + duration,\n\t\t\t\t'transform': 'translateX(0px)',\n\t\t\t\t'-webkit-transform': 'translateX(0px)'\n\t\t\t})\n\t\t}\n\t})\n\tsetStatus('close', instance, ownerInstance)\n}\n\n// status的状态发生变化\nfunction statusChange(newValue, oldValue, ownerInstance, instance) {\n\tvar state = instance.getState()\n\tif (state.disabled) return\n\t// 打开或关闭单元格\n\tif (newValue === 'close' && state.status === 'open') {\n\t\tcloseSwipeAction(instance, ownerInstance)\n\t} else if(newValue === 'open' && state.status === 'close') {\n\t\topenSwipeAction(instance, ownerInstance)\n\t}\n}\n\n// 菜单尺寸发生变化\nfunction sizeChange(newValue, oldValue, ownerInstance, instance) {\n\t// wxs内的局部变量快照\n\tvar state = instance.getState()\n\tstate.disabled = newValue.disabled\n\tstate.duration = newValue.duration\n\tstate.show = newValue.show\n\tstate.threshold = newValue.threshold\n\tstate.buttons = newValue.buttons\n\n\tif (state.buttons) {\n\t\tvar len = state.buttons.length\n\t\tvar buttonsWidth = 0\n\t\tvar buttons = newValue.buttons\n\t\tfor (var i = 0; i < len; i++) {\n\t\t\tbuttonsWidth += buttons[i].width\n\t\t}\n\t}\n\tstate.buttonsWidth = buttonsWidth\n}\n\nmodule.exports = {\n\ttouchstart: touchstart,\n\ttouchmove: touchmove,\n\ttouchend: touchend,\n\tsizeChange: sizeChange,\n\tstatusChange: statusChange\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action-item/nvue - backup.js",
    "content": "// nvue操作dom的库，用于获取dom的尺寸信息\nconst dom = uni.requireNativePlugin('dom')\n// nvue中用于操作元素动画的库，类似于uni.animation，只不过uni.animation不能用于nvue\nconst animation = uni.requireNativePlugin('animation')\n\nexport default {\n    data() {\n        return {\n            // 是否滑动中\n            moving: false,\n            // 状态，open-打开状态，close-关闭状态\n            status: 'close',\n            // 开始触摸点的X和Y轴坐标\n            startX: 0,\n            startY: 0,\n            // 所有隐藏按钮的尺寸信息数组\n            buttons: [],\n            // 所有按钮的总宽度\n            buttonsWidth: 0,\n            // 记录上一次移动的位置值\n            moveX: 0,\n            // 记录上一次滑动的位置，用于前后两次做对比，如果移动的距离小于某一阈值，则认为前后之间没有移动，为了解决可能存在的通信阻塞问题\n            lastX: 0\n        }\n    },\n    computed: {\n        // 获取过渡时间\n        getDuratin() {\n            let duration = String(this.duration)\n            // 如果ms为单位，返回ms的数值部分\n            if (duration.indexOf('ms') >= 0) return parseInt(duration)\n            // 如果s为单位，为了得到ms的数值，需要乘以1000\n            if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000\n            // 如果值传了数值，且小于30，认为是s单位\n            duration = Number(duration)\n            return duration < 30 ? duration * 1000 : duration\n        }\n    },\n    watch: {\n        show: {\n            immediate: true,\n            handler(n) {\n                // if(n === true) {\n                // \tuni.$u.sleep(50).then(() => {\n                // \t\tthis.openSwipeAction()\n                // \t})\n                // } else {\n                // \tthis.closeSwipeAction()\n                // }\n            }\n        }\n    },\n    mounted() {\n        uni.$u.sleep(20).then(() => {\n            this.queryRect()\n        })\n    },\n    methods: {\n        close() {\n            this.closeSwipeAction()\n        },\n        // 触摸单元格\n        touchstart(event) {\n            if (this.disabled) return\n            this.closeOther()\n            const { touches } = event\n            // 记录触摸开始点的坐标值\n            this.startX = touches[0].pageX\n            this.startY = touches[0].pageY\n        },\n        // // 触摸滑动\n        touchmove(event) {\n            if (this.disabled) return\n            const { touches } = event\n            const { pageX } = touches[0]\n            const { pageY } = touches[0]\n            let moveX = pageX - this.startX\n            const moveY = pageY - this.startY\n            const { buttonsWidth } = this\n            const len = this.buttons.length\n\n            // 判断前后两次的移动距离，如果小于一定值，则不进行移动处理\n            if (Math.abs(pageX - this.lastX) < 0.3) return\n            this.lastX = pageX\n\n            // 移动的X轴距离大于Y轴距离，也即终点与起点位置连线，与X轴夹角小于45度时，禁止页面滚动\n            if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > this.threshold) {\n                event.stopPropagation()\n            }\n            // 如果移动的X轴距离小于Y轴距离，也即终点位置与起点位置连线，与Y轴夹角小于45度时，认为是页面上下滑动，而不是左右滑动单元格\n            if (Math.abs(moveX) < Math.abs(moveY)) return\n\n            // 限制右滑的距离，不允许内容部分往右偏移，右滑会导致X轴偏移值大于0，以此做判断\n            // 此处不能直接return，因为滑动过程中会缺失某些关键点坐标，会导致错乱，最好的办法就是\n            // 在超出后，设置为0\n            if (this.status === 'open') {\n                // 在开启状态下，向左滑动，需忽略\n                if (moveX < 0) moveX = 0\n                // 想要收起菜单，最大能移动的距离为按钮的总宽度\n                if (moveX > buttonsWidth) moveX = buttonsWidth\n                // 如果是已经打开了的状态，向左滑动时，移动收起菜单\n                this.moveSwipeAction(-buttonsWidth + moveX)\n            } else {\n                // 关闭状态下，右滑动需忽略\n                if (moveX > 0) moveX = 0\n                // 滑动的距离不允许超过所有按钮的总宽度，此时只能是左滑，最终设置按钮的总宽度，同时为负数\n                if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth\n                // 只要是在滑过程中，就不断移动菜单的内容部分，从而使隐藏的菜单显示出来\n                this.moveSwipeAction(moveX)\n            }\n        },\n        // 单元格结束触摸\n        touchend(event) {\n            if (this.disabled) return\n            const touches = event.changedTouches ? event.changedTouches[0] : {}\n            const { pageX } = touches\n            const { pageY } = touches\n            const { buttonsWidth } = this\n            this.moveX = pageX - this.startX\n            if (this.status === 'open') {\n                // 在展开的状态下，继续左滑，无需操作\n                if (this.moveX < 0) this.moveX = 0\n                if (this.moveX > buttonsWidth) this.moveX = buttonsWidth\n                // 在开启状态下，点击一下内容区域，moveX为0，也即没有进行移动，这时执行收起菜单逻辑\n                if (this.moveX === 0) {\n                    return this.closeSwipeAction()\n                }\n                // 在开启状态下，滑动距离小于阈值，则默认为不关闭，同时恢复原来的打开状态\n                if (Math.abs(this.moveX) < this.threshold) {\n                    this.openSwipeAction()\n                } else {\n                    // 如果滑动距离大于阈值，则执行收起逻辑\n                    this.closeSwipeAction()\n                }\n            } else {\n                // 在关闭的状态下，右滑，无需操作\n                if (this.moveX >= 0) this.moveX = 0\n                if (this.moveX <= -buttonsWidth) this.moveX = -buttonsWidth\n                // 理由同上\n                if (Math.abs(this.moveX) < this.threshold) {\n                    this.closeSwipeAction()\n                } else {\n                    this.openSwipeAction()\n                }\n            }\n        },\n        // 移动滑动选择器内容区域，同时显示出其隐藏的菜单\n        moveSwipeAction(moveX) {\n            if (this.moving) return\n            this.moving = true\n\n            let previewButtonsMoveX = 0\n            const len = this.buttons.length\n            animation.transition(this.$refs['u-swipe-action-item__content'].ref, {\n                styles: {\n                    transform: `translateX(${moveX}px)`\n                },\n                timingFunction: 'linear'\n            }, () => {\n                this.moving = false\n            })\n            // 按钮的组的长度\n            for (let i = len - 1; i >= 0; i--) {\n                const buttonRef = this.$refs[`u-swipe-action-item__right__button-${i}`][0].ref\n                // 通过比例，得出元素自身该移动的距离\n                const translateX = this.buttons[i].width / this.buttonsWidth * moveX\n                // 最终移动的距离，是通过自身比例算出的距离，再加上在它之前所有按钮移动的距离之和\n                const realTranslateX = translateX + previewButtonsMoveX\n                animation.transition(buttonRef, {\n                    styles: {\n                        transform: `translateX(${realTranslateX}px)`\n                    },\n                    duration: 0,\n                    delay: 0,\n                    timingFunction: 'linear'\n                }, () => {})\n                // 记录本按钮之前的所有按钮的移动距离之和\n                previewButtonsMoveX += translateX\n            }\n        },\n        // 关闭菜单\n        closeSwipeAction() {\n            if (this.status === 'close') return\n            this.moving = true\n            const { buttonsWidth } = this\n            animation.transition(this.$refs['u-swipe-action-item__content'].ref, {\n                styles: {\n                    transform: 'translateX(0px)'\n                },\n                duration: this.getDuratin,\n                timingFunction: 'ease-in-out'\n            }, () => {\n                this.status = 'close'\n                this.moving = false\n                this.closeHandler()\n            })\n            // 按钮的组的长度\n            const len = this.buttons.length\n            for (let i = len - 1; i >= 0; i--) {\n                const buttonRef = this.$refs[`u-swipe-action-item__right__button-${i}`][0].ref\n                // 如果不满足边界条件，返回\n                if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return\n\n                animation.transition(buttonRef, {\n                    styles: {\n                        transform: 'translateX(0px)'\n                    },\n                    duration: this.getDuratin,\n                    timingFunction: 'ease-in-out'\n                }, () => {})\n            }\n        },\n        // 打开菜单\n        openSwipeAction() {\n            if (this.status === 'open') return\n            this.moving = true\n            const buttonsWidth = -this.buttonsWidth\n            let previewButtonsMoveX = 0\n            animation.transition(this.$refs['u-swipe-action-item__content'].ref, {\n                styles: {\n                    transform: `translateX(${buttonsWidth}px)`\n                },\n                duration: this.getDuratin,\n                timingFunction: 'ease-in-out'\n            }, () => {\n                this.status = 'open'\n                this.moving = false\n                this.openHandler()\n            })\n            // 按钮的组的长度\n            const len = this.buttons.length\n            for (let i = len - 1; i >= 0; i--) {\n                const buttonRef = this.$refs[`u-swipe-action-item__right__button-${i}`][0].ref\n                // 如果不满足边界条件，返回\n                if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return\n                // 通过比例，得出元素自身该移动的距离\n                const translateX = this.buttons[i].width / this.buttonsWidth * buttonsWidth\n                // 最终移动的距离，是通过自身比例算出的距离，再加上在它之前所有按钮移动的距离之和\n                const realTranslateX = translateX + previewButtonsMoveX\n                animation.transition(buttonRef, {\n                    styles: {\n                        transform: `translateX(${realTranslateX}px)`\n                    },\n                    duration: this.getDuratin,\n                    timingFunction: 'ease-in-out'\n                }, () => {})\n                previewButtonsMoveX += translateX\n            }\n        },\n        // 查询按钮节点信息\n        queryRect() {\n            // 历遍所有按钮数组，通过getRectByDom返回一个promise\n            const promiseAll = this.rightOptions.map((item, index) => this.getRectByDom(this.$refs[`u-swipe-action-item__right__button-${index}`][0]))\n            // 通过promise.all方法，让所有按钮的查询结果返回一个数组的形式\n            Promise.all(promiseAll).then((sizes) => {\n                this.buttons = sizes\n                // 计算所有按钮总宽度\n                this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)\n            })\n        },\n        // 通过nvue的dom模块，查询节点信息\n        getRectByDom(ref) {\n            return new Promise((resolve) => {\n                dom.getComponentRect(ref, (res) => {\n                    resolve(res.size)\n                })\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action-item/nvue.js",
    "content": "// nvue操作dom的库，用于获取dom的尺寸信息\nconst dom = uni.requireNativePlugin('dom');\nconst bindingX = uni.requireNativePlugin('bindingx');\nconst animation = uni.requireNativePlugin('animation');\n\nexport default {\n\tdata() {\n\t\treturn {\n\t\t\t// 所有按钮的总宽度\n\t\t\tbuttonsWidth: 0,\n\t\t\t// 是否正在移动中\n\t\t\tmoving: false\n\t\t}\n\t},\n\tcomputed: {\n\t\t// 获取过渡时间\n\t\tgetDuratin() {\n\t\t\tlet duration = String(this.duration)\n\t\t\t// 如果ms为单位，返回ms的数值部分\n\t\t\tif (duration.indexOf('ms') >= 0) return parseInt(duration)\n\t\t\t// 如果s为单位，为了得到ms的数值，需要乘以1000\n\t\t\tif (duration.indexOf('s') >= 0) return parseInt(duration) * 1000\n\t\t\t// 如果值传了数值，且小于30，认为是s单位\n\t\t\tduration = Number(duration)\n\t\t\treturn duration < 30 ? duration * 1000 : duration\n\t\t}\n\t},\n\twatch: {\n\t\tshow(n) {\n\t\t\tif(n) {\n\t\t\t\tthis.moveCellByAnimation('open') \n\t\t\t} else {\n\t\t\t\tthis.moveCellByAnimation('close') \n\t\t\t}\n\t\t}\n\t},\n\tmounted() {\n\t\tthis.initialize()\n\t},\n\tmethods: {\n\t\tinitialize() {\n\t\t\tthis.queryRect() \n\t\t},\n\t\t// 关闭单元格，用于打开一个，自动关闭其他单元格的场景\n\t\tcloseHandler() {\n\t\t\tif(this.status === 'open') {\n\t\t\t\t// 如果在打开状态下，进行点击的话，直接关闭单元格\n\t\t\t\treturn this.moveCellByAnimation('close') && this.unbindBindingX()\n\t\t\t}\n\t\t},\n\t\t// 点击单元格\n\t\tclickHandler() {\n\t\t\t// 如果在移动中被点击，进行忽略\n\t\t\tif(this.moving) return\n\t\t\t// 尝试关闭其他打开的单元格\n\t\t\tthis.parent && this.parent.closeOther(this)\n\t\t\tif(this.status === 'open') {\n\t\t\t\t// 如果在打开状态下，进行点击的话，直接关闭单元格\n\t\t\t\treturn this.moveCellByAnimation('close') && this.unbindBindingX()\n\t\t\t}\n\t\t},\n\t\t// 滑动单元格\n\t\tonTouchstart(e) {\n\t\t\t// 如果当前正在移动中，或者disabled状态，则返回\n\t\t\tif(this.moving || this.disabled) { \n\t\t\t\treturn this.unbindBindingX()   \n\t\t\t}\n\t\t\tif(this.status === 'open') {\n\t\t\t\t// 如果在打开状态下，进行点击的话，直接关闭单元格\n\t\t\t\treturn this.moveCellByAnimation('close') && this.unbindBindingX()\n\t\t\t}\n\t\t\t// 特殊情况下，e可能不为一个对象\n\t\t\te?.stopPropagation && e.stopPropagation() \n\t\t\te?.preventDefault && e.preventDefault()\n\t\t\tthis.moving = true\n\t\t\t// 获取元素ref\n\t\t\tconst content = this.getContentRef()\n\t\t\tlet expression = `min(max(${-this.buttonsWidth}, x), 0)`\n\t\t\t// 尝试关闭其他打开的单元格\n\t\t\tthis.parent && this.parent.closeOther(this)\n\t\t\t\n\t\t\t// 阿里为了KPI而开源的BindingX\n\t\t\tthis.panEvent = bindingX.bind({\n\t\t\t\tanchor: content,\n\t\t\t\teventType: 'pan',\n\t\t\t\tprops: [{\n\t\t\t\t\telement: content,\n\t\t\t\t\t// 绑定width属性，设置其宽度值\n\t\t\t\t\tproperty: 'transform.translateX',\n\t\t\t\t\texpression\n\t\t\t\t}]\n\t\t\t}, (res) => {\n\t\t\t\tthis.moving = false\n\t\t\t\tif (res.state === 'end' || res.state === 'exit') {\n\t\t\t\t\tconst deltaX = res.deltaX\n\t\t\t\t\tif(deltaX <= -this.buttonsWidth || deltaX >= 0) {\n\t\t\t\t\t\t// 如果触摸滑动的过程中，大于单元格的总宽度，或者大于0，意味着已经动过滑动达到了打开或者关闭的状态\n\t\t\t\t\t\t// 这里直接进行状态的标记\n\t\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\t\tthis.status = deltaX <= -this.buttonsWidth ? 'open' : 'close'\n\t\t\t\t\t\t})\n\t\t\t\t\t} else if(Math.abs(deltaX) > uni.$u.getPx(this.threshold)) {\n\t\t\t\t\t\t// 在移动大于阈值、并且小于总按钮宽度时，进行自动打开或者关闭\n\t\t\t\t\t\t// 移动距离大于0时，意味着需要关闭状态\n\t\t\t\t\t\tif(Math.abs(deltaX) < this.buttonsWidth) {\n\t\t\t\t\t\t\tthis.moveCellByAnimation(deltaX > 0 ? 'close' : 'open')\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// 在小于阈值时，进行关闭操作(如果在打开状态下，将不会执行bindingX)\n\t\t\t\t\t\tthis.moveCellByAnimation('close')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\t// 释放bindingX\n\t\tunbindBindingX() {\n\t\t\t// 释放上一次的资源\n\t\t\tif (this?.panEvent?.token != 0) {\n\t\t\t\tbindingX.unbind({\n\t\t\t\t\ttoken: this.panEvent?.token,\n\t\t\t\t\t// pan为手势事件\n\t\t\t\t\teventType: 'pan'\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\t// 查询按钮节点信息\n\t\tqueryRect() {\n\t\t\t// 历遍所有按钮数组，通过getRectByDom返回一个promise\n\t\t\tconst promiseAll = this.options.map((item, index) => {\n\t\t\t\treturn this.getRectByDom(this.$refs[`u-swipe-action-item__right__button-${index}`][0])\n\t\t\t})\n\t\t\t// 通过promise.all方法，让所有按钮的查询结果返回一个数组的形式\n\t\t\tPromise.all(promiseAll).then(sizes => {\n\t\t\t\tthis.buttons = sizes\n\t\t\t\t// 计算所有按钮总宽度\n\t\t\t\tthis.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)\n\t\t\t})\n\t\t},\n\t\t// 通过nvue的dom模块，查询节点信息\n\t\tgetRectByDom(ref) {\n\t\t\treturn new Promise(resolve => {\n\t\t\t\tdom.getComponentRect(ref, res => {\n\t\t\t\t\tresolve(res.size)\n\t\t\t\t})\n\t\t\t}) \n\t\t},\n\t\t// 移动单元格到左边或者右边尽头\n\t\tmoveCellByAnimation(status = 'open') {\n\t\t\tif(this.moving) return\n\t\t\t// 标识当前状态\n\t\t\tthis.moveing = true\n\t\t\tconst content = this.getContentRef()\n\t\t\tconst x = status === 'open' ? -this.buttonsWidth : 0 \n\t\t\tanimation.transition(content, {\n\t\t\t\tstyles: {\n\t\t\t\t\ttransform: `translateX(${x}px)`,\n\t\t\t\t},\n\t\t\t\tduration: uni.$u.getDuration(this.duration, false),\n\t\t\t\ttimingFunction: 'ease-in-out'\n\t\t\t}, () => {\n\t\t\t\tthis.moving = false\n\t\t\t\tthis.status = status\n\t\t\t\tthis.unbindBindingX()\n\t\t\t})\n\t\t},\n\t\t// 获取元素ref\n\t\tgetContentRef() {\n\t\t\treturn this.$refs['u-swipe-action-item__content'].ref\n\t\t},\n\t\tbeforeDestroy() {\n\t\t\tthis.unbindBindingX()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action-item/props.js",
    "content": "export default {\n    props: {\n        // 控制打开或者关闭\n        show: {\n            type: Boolean,\n            default: uni.$u.props.swipeActionItem.show\n        },\n        // 标识符，如果是v-for，可用index索引值\n        name: {\n            type: [String, Number],\n            default: uni.$u.props.swipeActionItem.name\n        },\n        // 是否禁用\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.swipeActionItem.disabled\n        },\n        // 是否自动关闭其他swipe按钮组\n        autoClose: {\n            type: Boolean,\n            default: uni.$u.props.swipeActionItem.autoClose\n        },\n        // 滑动距离阈值，只有大于此值，才被认为是要打开菜单\n        threshold: {\n            type: Number,\n            default: uni.$u.props.swipeActionItem.threshold\n        },\n        // 右侧按钮内容\n        options: {\n            type: Array,\n            default() {\n                return uni.$u.props.swipeActionItem.rightOptions\n            }\n        },\n        // 动画过渡时间，单位ms\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.swipeActionItem.duration\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action-item/u-swipe-action-item.vue",
    "content": "<template>\n\t<view class=\"u-swipe-action-item\" ref=\"u-swipe-action-item\">\n\t\t<view class=\"u-swipe-action-item__right\">\n\t\t\t<slot name=\"button\">\n\t\t\t\t<view v-for=\"(item,index) in options\" :key=\"index\" class=\"u-swipe-action-item__right__button\"\n\t\t\t\t\t:ref=\"`u-swipe-action-item__right__button-${index}`\" :style=\"[{\n\t\t\t\t\t\talignItems: item.style && item.style.borderRadius ? 'center' : 'stretch'\n\t\t\t\t\t}]\" @tap=\"buttonClickHandler(item, index)\">\n\t\t\t\t\t<view class=\"u-swipe-action-item__right__button__wrapper\" :style=\"[{\n\t\t\t\t\t\t\tbackgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',\n\t\t\t\t\t\t\tborderRadius: item.style && item.style.borderRadius ? item.style.borderRadius : '0',\n\t\t\t\t\t\t\tpadding: item.style && item.style.borderRadius ? '0' : '0 15px',\n\t\t\t\t\t\t}, item.style]\">\n\t\t\t\t\t\t<u-icon v-if=\"item.icon\" :name=\"item.icon\"\n\t\t\t\t\t\t\t:color=\"item.style && item.style.color ? item.style.color : '#ffffff'\"\n\t\t\t\t\t\t\t:size=\"item.iconSize ? $u.addUnit(item.iconSize) : item.style && item.style.fontSize ? $u.getPx(item.style.fontSize) * 1.2 : 17\"\n\t\t\t\t\t\t\t:customStyle=\"{\n\t\t\t\t\t\t\t\tmarginRight: item.text ? '2px' : 0\n\t\t\t\t\t\t\t}\"></u-icon>\n\t\t\t\t\t\t<text v-if=\"item.text\" class=\"u-swipe-action-item__right__button__wrapper__text u-line-1\"\n\t\t\t\t\t\t\t:style=\"[{\n\t\t\t\t\t\t\t\tcolor: item.style && item.style.color ? item.style.color : '#ffffff',\n\t\t\t\t\t\t\t\tfontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px',\n\t\t\t\t\t\t\t\tlineHeight: item.style && item.style.fontSize ? item.style.fontSize : '16px',\n\t\t\t\t\t\t\t}]\">{{ item.text }}</text>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</slot>\n\t\t</view>\n\t\t<!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->\n\t\t<view class=\"u-swipe-action-item__content\" @touchstart=\"wxs.touchstart\" @touchmove=\"wxs.touchmove\"\n\t\t\t@touchend=\"wxs.touchend\" :status=\"status\" :change:status=\"wxs.statusChange\" :size=\"size\"\n\t\t\t:change:size=\"wxs.sizeChange\">\n\t\t\t<!-- #endif -->\n\t\t\t<!-- #ifdef APP-NVUE -->\n\t\t\t<view class=\"u-swipe-action-item__content\" ref=\"u-swipe-action-item__content\" @panstart=\"onTouchstart\"\n\t\t\t\t@tap=\"clickHandler\">\n\t\t\t\t<!-- #endif -->\n\t\t\t\t<slot />\n\t\t\t</view>\n\t\t</view>\n</template>\n<!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->\n<script src=\"./index.wxs\" module=\"wxs\" lang=\"wxs\"></script>\n<!-- #endif -->\n<script>\n\timport touch from '../../libs/mixin/touch.js'\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\timport nvue from './nvue.js';\n\t// #endif\n\t// #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ\n\timport wxs from './wxs.js';\n\t// #endif\n\t/**\n\t * SwipeActionItem 滑动单元格子组件\n\t * @description 该组件一般用于左滑唤出操作菜单的场景，用的最多的是左滑删除操作\n\t * @tutorial https://www.uviewui.com/components/swipeAction.html\n\t * @property {Boolean}\t\t\tshow\t\t\t控制打开或者关闭（默认 false ）\n\t * @property {String | Number}\tindex\t\t\t标识符，如果是v-for，可用index索引\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用（默认 false ）\n\t * @property {Boolean}\t\t\tautoClose\t\t是否自动关闭其他swipe按钮组（默认 true ）\n\t * @property {Number}\t\t\tthreshold\t\t滑动距离阈值，只有大于此值，才被认为是要打开菜单（默认 30 ）\n\t * @property {Array}\t\t\toptions\t\t\t右侧按钮内容\n\t * @property {String | Number}\tduration\t\t动画过渡时间，单位ms（默认 350 ）\n\t * @event {Function(index)}\topen\t组件打开时触发\n\t * @event {Function(index)}\tclose\t组件关闭时触发\n\t * @example\t<u-swipe-action><u-swipe-action-item :options=\"options1\" ></u-swipe-action-item></u-swipe-action>\n\t */\n\texport default {\n\t\tname: 'u-swipe-action-item',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props, touch],\n\t\t// #ifdef APP-NVUE\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props, nvue, touch],\n\t\t// #endif\n\t\t// #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props, touch, wxs],\n\t\t// #endif\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 按钮的尺寸信息\n\t\t\t\tsize: {},\n\t\t\t\t// 父组件u-swipe-action的参数\n\t\t\t\tparentData: {\n\t\t\t\t\tautoClose: true,\n\t\t\t\t},\n\t\t\t\t// 当前状态，open-打开，close-关闭\n\t\t\t\tstatus: 'close',\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t// 由于wxs无法直接读取外部的值，需要在外部值变化时，重新执行赋值逻辑\n\t\t\twxsInit(newValue, oldValue) {\n\t\t\t\tthis.queryRect()\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\twxsInit() {\n\t\t\t\treturn [this.disabled, this.autoClose, this.threshold, this.options, this.duration]\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 初始化父组件数据\n\t\t\t\tthis.updateParentData()\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tuni.$u.sleep().then(() => {\n\t\t\t\t\tthis.queryRect()\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\tupdateParentData() {\n\t\t\t\t// 此方法在mixin中\n\t\t\t\tthis.getParentData('u-swipe-action')\n\t\t\t},\n\t\t\t// #ifndef APP-NVUE\n\t\t\t// 查询节点\n\t\t\tqueryRect() {\n\t\t\t\tthis.$uGetRect('.u-swipe-action-item__right__button', true).then(buttons => {\n\t\t\t\t\tthis.size = {\n\t\t\t\t\t\tbuttons,\n\t\t\t\t\t\tshow: this.show,\n\t\t\t\t\t\tdisabled: this.disabled,\n\t\t\t\t\t\tthreshold: this.threshold,\n\t\t\t\t\t\tduration: this.duration\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t},\n\t\t\t// #endif\n\t\t\t// 按钮被点击\n\t\t\tbuttonClickHandler(item, index) {\n\t\t\t\tthis.$emit('click', {\n\t\t\t\t\tindex,\n\t\t\t\t\tname: this.name\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-swipe-action-item {\n\t\tposition: relative;\n\t\toverflow: hidden;\n\t\t/* #ifndef APP-NVUE || MP-WEIXIN */\n\t\ttouch-action: pan-y;\n\t\t/* #endif */\n\n\t\t&__content {\n\t\t\tbackground-color: #FFFFFF;\n\t\t\tz-index: 10;\n\t\t}\n\n\t\t&__right {\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\tbottom: 0;\n\t\t\tright: 0;\n\t\t\t@include flex;\n\n\t\t\t&__button {\n\t\t\t\t@include flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\toverflow: hidden;\n\t\t\t\talign-items: center;\n\n\t\t\t\t&__wrapper {\n\t\t\t\t\t@include flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\tpadding: 0 15px;\n\n\t\t\t\t\t&__text {\n\t\t\t\t\t\t@include flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tcolor: #FFFFFF;\n\t\t\t\t\t\tfont-size: 15px;\n\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\tjustify-content: center;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swipe-action-item/wxs.js",
    "content": "export default {\n    methods: {\n        // 关闭时执行\n        closeHandler() {\n            this.status = 'close'\n        },\n        setState(status) {\n            this.status = status\n        },\n        closeOther() {\n            // 尝试关闭其他打开的单元格\n            this.parent && this.parent.closeOther(this)\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swiper/props.js",
    "content": "export default {\n    props: {\n        // 列表数组，元素可为字符串，如为对象可通过keyName指定目标属性名\n        list: {\n            type: Array,\n            default: uni.$u.props.swiper.list\n        },\n        // 是否显示面板指示器\n        indicator: {\n            type: Boolean,\n            default: uni.$u.props.swiper.indicator\n        },\n        // 指示器非激活颜色\n        indicatorActiveColor: {\n            type: String,\n            default: uni.$u.props.swiper.indicatorActiveColor\n        },\n        // 指示器的激活颜色\n        indicatorInactiveColor: {\n            type: String,\n            default: uni.$u.props.swiper.indicatorInactiveColor\n        },\n        // 指示器样式，可通过bottom，left，right进行定位\n        indicatorStyle: {\n            type: [String, Object],\n            default: uni.$u.props.swiper.indicatorStyle\n        },\n        // 指示器模式，line-线型，dot-点型\n        indicatorMode: {\n            type: String,\n            default: uni.$u.props.swiper.indicatorMode\n        },\n        // 是否自动切换\n        autoplay: {\n            type: Boolean,\n            default: uni.$u.props.swiper.autoplay\n        },\n        // 当前所在滑块的 index\n        current: {\n            type: [String, Number],\n            default: uni.$u.props.swiper.current\n        },\n        // 当前所在滑块的 item-id ，不能与 current 被同时指定\n        currentItemId: {\n            type: String,\n            default: uni.$u.props.swiper.currentItemId\n        },\n        // 滑块自动切换时间间隔\n        interval: {\n            type: [String, Number],\n            default: uni.$u.props.swiper.interval\n        },\n        // 滑块切换过程所需时间\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.swiper.duration\n        },\n        // 播放到末尾后是否重新回到开头\n        circular: {\n            type: Boolean,\n            default: uni.$u.props.swiper.circular\n        },\n        // 前边距，可用于露出前一项的一小部分，nvue和支付宝不支持\n        previousMargin: {\n            type: [String, Number],\n            default: uni.$u.props.swiper.previousMargin\n        },\n        // 后边距，可用于露出后一项的一小部分，nvue和支付宝不支持\n        nextMargin: {\n            type: [String, Number],\n            default: uni.$u.props.swiper.nextMargin\n        },\n        // 当开启时，会根据滑动速度，连续滑动多屏，支付宝不支持\n        acceleration: {\n            type: Boolean,\n            default: uni.$u.props.swiper.acceleration\n        },\n        // 同时显示的滑块数量，nvue、支付宝小程序不支持\n        displayMultipleItems: {\n            type: Number,\n            default: uni.$u.props.swiper.displayMultipleItems\n        },\n        // 指定swiper切换缓动动画类型，有效值：default、linear、easeInCubic、easeOutCubic、easeInOutCubic\n        // 只对微信小程序有效\n        easingFunction: {\n            type: String,\n            default: uni.$u.props.swiper.easingFunction\n        },\n        // list数组中指定对象的目标属性名\n        keyName: {\n            type: String,\n            default: uni.$u.props.swiper.keyName\n        },\n        // 图片的裁剪模式\n        imgMode: {\n            type: String,\n            default: uni.$u.props.swiper.imgMode\n        },\n        // 组件高度\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.swiper.height\n        },\n        // 背景颜色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.swiper.bgColor\n        },\n        // 组件圆角，数值或带单位的字符串\n        radius: {\n            type: [String, Number],\n            default: uni.$u.props.swiper.radius\n        },\n        // 是否加载中\n        loading: {\n            type: Boolean,\n            default: uni.$u.props.swiper.loading\n        },\n        // 是否显示标题，要求数组对象中有title属性\n        showTitle: {\n            type: Boolean,\n            default: uni.$u.props.swiper.showTitle\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swiper/u-swiper.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-swiper\"\n\t\t:style=\"{\n\t\t\tbackgroundColor: bgColor,\n\t\t\theight: $u.addUnit(height),\n\t\t\tborderRadius: $u.addUnit(radius)\n\t\t}\"\n\t>\n\t\t<view\n\t\t\tclass=\"u-swiper__loading\"\n\t\t\tv-if=\"loading\"\n\t\t>\n\t\t\t<u-loading-icon mode=\"circle\"></u-loading-icon>\n\t\t</view>\n\t\t<swiper\n\t\t\tv-else\n\t\t\tclass=\"u-swiper__wrapper\"\n\t\t\t:style=\"{\n\t\t\t\theight: $u.addUnit(height),\n\t\t\t}\"\n\t\t\t@change=\"change\"\n\t\t\t:circular=\"circular\"\n\t\t\t:interval=\"interval\"\n\t\t\t:duration=\"duration\"\n\t\t\t:autoplay=\"autoplay\"\n\t\t\t:current=\"current\"\n\t\t\t:currentItemId=\"currentItemId\"\n\t\t\t:previousMargin=\"$u.addUnit(previousMargin)\"\n\t\t\t:nextMargin=\"$u.addUnit(nextMargin)\"\n\t\t\t:acceleration=\"acceleration\"\n\t\t\t:displayMultipleItems=\"displayMultipleItems\"\n\t\t\t:easingFunction=\"easingFunction\"\n\t\t>\n\t\t\t<swiper-item\n\t\t\t\tclass=\"u-swiper__wrapper__item\"\n\t\t\t\tv-for=\"(item, index) in list\"\n\t\t\t\t:key=\"index\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-swiper__wrapper__item__wrapper\"\n\t\t\t\t\t:style=\"[itemStyle(index)]\"\n\t\t\t\t>\n\t\t\t\t\t<!-- 在nvue中，image图片的宽度默认为屏幕宽度，需要通过flex:1撑开，另外必须设置高度才能显示图片 -->\n\t\t\t\t\t<image\n\t\t\t\t\t\tclass=\"u-swiper__wrapper__item__wrapper__image\"\n\t\t\t\t\t\tv-if=\"getItemType(item) === 'image'\"\n\t\t\t\t\t\t:src=\"getSource(item)\"\n\t\t\t\t\t\t:mode=\"imgMode\"\n\t\t\t\t\t\t@tap=\"clickHandler(index)\"\n\t\t\t\t\t\t:style=\"{\n\t\t\t\t\t\t\theight: $u.addUnit(height),\n\t\t\t\t\t\t\tborderRadius: $u.addUnit(radius)\n\t\t\t\t\t\t}\"\n\t\t\t\t\t></image>\n\t\t\t\t\t<video\n\t\t\t\t\t\tclass=\"u-swiper__wrapper__item__wrapper__video\"\n\t\t\t\t\t\tv-if=\"getItemType(item) === 'video'\"\n\t\t\t\t\t\t:id=\"`video-${index}`\"\n\t\t\t\t\t\t:enable-progress-gesture=\"false\"\n\t\t\t\t\t\t:src=\"getSource(item)\"\n\t\t\t\t\t\t:poster=\"getPoster(item)\"\n\t\t\t\t\t\t:title=\"showTitle && $u.test.object(item) && item.title ? item.title : ''\"\n\t\t\t\t\t\t:style=\"{\n\t\t\t\t\t\t\theight: $u.addUnit(height)\n\t\t\t\t\t\t}\"\n\t\t\t\t\t\tcontrols\n\t\t\t\t\t\t@tap=\"clickHandler(index)\"\n\t\t\t\t\t></video>\n\t\t\t\t\t<text\n\t\t\t\t\t\tv-if=\"showTitle && $u.test.object(item) && item.title && $u.test.image(getSource(item))\"\n\t\t\t\t\t\tclass=\"u-swiper__wrapper__item__wrapper__title u-line-1\"\n\t\t\t\t\t>{{ item.title }}</text>\n\t\t\t\t</view>\n\t\t\t</swiper-item>\n\t\t</swiper>\n\t\t<view class=\"u-swiper__indicator\" :style=\"[$u.addStyle(indicatorStyle)]\">\n\t\t\t<slot name=\"indicator\">\n\t\t\t\t<u-swiper-indicator\n\t\t\t\t\tv-if=\"!loading && indicator && !showTitle\"\n\t\t\t\t\t:indicatorActiveColor=\"indicatorActiveColor\"\n\t\t\t\t\t:indicatorInactiveColor=\"indicatorInactiveColor\"\n\t\t\t\t\t:length=\"list.length\"\n\t\t\t\t\t:current=\"currentIndex\"\n\t\t\t\t\t:indicatorMode=\"indicatorMode\"\n\t\t\t\t></u-swiper-indicator>\n\t\t\t</slot>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Swiper 轮播图\n\t * @description 该组件一般用于导航轮播，广告展示等场景,可开箱即用，\n\t * @tutorial https://www.uviewui.com/components/swiper.html\n\t * @property {Array}\t\t\tlist\t\t\t\t\t轮播图数据\n\t * @property {Boolean}\t\t\tindicator\t\t\t\t是否显示面板指示器（默认 false ）\n\t * @property {String}\t\t\tindicatorActiveColor\t指示器非激活颜色（默认 '#FFFFFF' ）\n\t * @property {String}\t\t\tindicatorInactiveColor\t指示器的激活颜色（默认 'rgba(255, 255, 255, 0.35)' ）\n\t * @property {String | Object}\tindicatorStyle\t\t\t指示器样式，可通过bottom，left，right进行定位\n\t * @property {String}\t\t\tindicatorMode\t\t\t指示器模式（默认 'line' ）\n\t * @property {Boolean}\t\t\tautoplay\t\t\t\t是否自动切换（默认 true ）\n\t * @property {String | Number}\tcurrent\t\t\t\t\t当前所在滑块的 index（默认 0 ）\n\t * @property {String}\t\t\tcurrentItemId\t\t\t当前所在滑块的 item-id ，不能与 current 被同时指定\n\t * @property {String | Number}\tinterval\t\t\t\t滑块自动切换时间间隔（ms）（默认 3000 ）\n\t * @property {String | Number}\tduration\t\t\t\t滑块切换过程所需时间（ms）（默认 300 ）\n\t * @property {Boolean}\t\t\tcircular\t\t\t\t播放到末尾后是否重新回到开头（默认 false ）\n\t * @property {String | Number}\tpreviousMargin\t\t\t前边距，可用于露出前一项的一小部分，nvue和支付宝不支持（默认 0 ）\n\t * @property {String | Number}\tnextMargin\t\t\t\t后边距，可用于露出后一项的一小部分，nvue和支付宝不支持（默认 0 ）\n\t * @property {Boolean}\t\t\tacceleration\t\t\t当开启时，会根据滑动速度，连续滑动多屏，支付宝不支持（默认 false ）\n\t * @property {Number}\t\t\tdisplayMultipleItems\t同时显示的滑块数量，nvue、支付宝小程序不支持（默认 1 ）\n\t * @property {String}\t\t\teasingFunction\t\t\t指定swiper切换缓动动画类型， 只对微信小程序有效（默认 'default' ）\n\t * @property {String}\t\t\tkeyName\t\t\t\t\tlist数组中指定对象的目标属性名（默认 'url' ）\n\t * @property {String}\t\t\timgMode\t\t\t\t\t图片的裁剪模式（默认 'aspectFill' ）\n\t * @property {String | Number}\theight\t\t\t\t\t组件高度（默认 130 ）\n\t * @property {String}\t\t\tbgColor\t\t\t\t\t背景颜色（默认 \t'#f3f4f6' ）\n\t * @property {String | Number}\tradius\t\t\t\t\t组件圆角，数值或带单位的字符串（默认 4 ）\n\t * @property {Boolean}\t\t\tloading\t\t\t\t\t是否加载中（默认 false ）\n\t * @property {Boolean}\t\t\tshowTitle\t\t\t\t是否显示标题，要求数组对象中有title属性（默认 false ）\n\t * @event {Function(index)}\tclick\t点击轮播图时触发\tindex：点击了第几张图片，从0开始\n\t * @event {Function(index)}\tchange\t轮播图切换时触发(自动或者手动切换)\tindex：切换到了第几张图片，从0开始\n\t * @example\t<u-swiper :list=\"list4\" keyName=\"url\" :autoplay=\"false\"></u-swiper>\n\t */\n\texport default {\n\t\tname: 'u-swiper',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tcurrentIndex: 0\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tcurrent(val, preVal) {\n\t\t\t\tif(val === preVal) return;\n\t\t\t\tthis.currentIndex = val; // 和上游数据关联上\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\titemStyle() {\n\t\t\t\treturn index => {\n\t\t\t\t\tconst style = {}\n\t\t\t\t\t// #ifndef APP-NVUE || MP-TOUTIAO\n\t\t\t\t\t// 左右流出空间的写法不支持nvue和头条\n\t\t\t\t\t// 只有配置了此二值，才加上对应的圆角，以及缩放\n\t\t\t\t\tif (this.nextMargin && this.previousMargin) {\n\t\t\t\t\t\tstyle.borderRadius = uni.$u.addUnit(this.radius)\n\t\t\t\t\t\tif (index !== this.currentIndex) style.transform = 'scale(0.92)'\n\t\t\t\t\t}\n\t\t\t\t\t// #endif\n\t\t\t\t\treturn style\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tmethods: {\n      getItemType(item) {\n        if (typeof item === 'string') return uni.$u.test.video(this.getSource(item)) ? 'video' : 'image'\n        if (typeof item === 'object' && this.keyName) {\n          if (!item.type) return uni.$u.test.video(this.getSource(item)) ? 'video' : 'image'\n          if (item.type === 'image') return 'image'\n          if (item.type === 'video') return 'video'\n          return 'image'\n        }\n      },\n\t\t\t// 获取目标路径，可能数组中为字符串，对象的形式，额外可指定对象的目标属性名keyName\n\t\t\tgetSource(item) {\n\t\t\t\tif (typeof item === 'string') return item\n\t\t\t\tif (typeof item === 'object' && this.keyName) return item[this.keyName]\n\t\t\t\telse uni.$u.error('请按格式传递列表参数')\n\t\t\t\treturn ''\n\t\t\t},\n\t\t\t// 轮播切换事件\n\t\t\tchange(e) {\n\t\t\t\t// 当前的激活索引\n\t\t\t\tconst {\n\t\t\t\t\tcurrent\n\t\t\t\t} = e.detail\n\t\t\t\tthis.pauseVideo(this.currentIndex)\n\t\t\t\tthis.currentIndex = current\n\t\t\t\tthis.$emit('change', e.detail)\n\t\t\t},\n\t\t\t// 切换轮播时，暂停视频播放\n\t\t\tpauseVideo(index) {\n\t\t\t\tconst lastItem = this.getSource(this.list[index])\n\t\t\t\tif (uni.$u.test.video(lastItem)) {\n\t\t\t\t\t// 当视频隐藏时，暂停播放\n\t\t\t\t\tconst video = uni.createVideoContext(`video-${index}`, this)\n\t\t\t\t\tvideo.pause()\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 当一个轮播item为视频时，获取它的视频海报\n\t\t\tgetPoster(item) {\n\t\t\t\treturn typeof item === 'object' && item.poster ? item.poster : ''\n\t\t\t},\n\t\t\t// 点击某个item\n\t\t\tclickHandler(index) {\n\t\t\t\tthis.$emit('click', index)\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-swiper {\n\t\t@include flex;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tposition: relative;\n\t\toverflow: hidden;\n\n\t\t&__wrapper {\n\t\t\tflex: 1;\n\n\t\t\t&__item {\n\t\t\t\tflex: 1;\n\n\t\t\t\t&__wrapper {\n\t\t\t\t\t@include flex;\n\t\t\t\t\tposition: relative;\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\ttransition: transform 0.3s;\n\t\t\t\t\tflex: 1;\n\n\t\t\t\t\t&__image {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t}\n\n\t\t\t\t\t&__video {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t}\n\n\t\t\t\t\t&__title {\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tbackground-color: rgba(0, 0, 0, 0.3);\n\t\t\t\t\t\tbottom: 0;\n\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\tright: 0;\n\t\t\t\t\t\tfont-size: 28rpx;\n\t\t\t\t\t\tpadding: 12rpx 24rpx;\n\t\t\t\t\t\tcolor: #FFFFFF;\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&__indicator {\n\t\t\tposition: absolute;\n\t\t\tbottom: 10px;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swiper-indicator/props.js",
    "content": "export default {\n    props: {\n        // 轮播的长度\n        length: {\n            type: [String, Number],\n            default: uni.$u.props.swiperIndicator.length\n        },\n        // 当前处于活动状态的轮播的索引\n        current: {\n            type: [String, Number],\n            default: uni.$u.props.swiperIndicator.current\n        },\n        // 指示器非激活颜色\n        indicatorActiveColor: {\n            type: String,\n            default: uni.$u.props.swiperIndicator.indicatorActiveColor\n        },\n        // 指示器的激活颜色\n        indicatorInactiveColor: {\n            type: String,\n            default: uni.$u.props.swiperIndicator.indicatorInactiveColor\n        },\n\t\t// 指示器模式，line-线型，dot-点型\n\t\tindicatorMode: {\n\t\t    type: String,\n\t\t    default: uni.$u.props.swiperIndicator.indicatorMode\n\t\t}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-swiper-indicator/u-swiper-indicator.vue",
    "content": "<template>\n\t<view class=\"u-swiper-indicator\">\n\t\t<view\n\t\t\tclass=\"u-swiper-indicator__wrapper\"\n\t\t\tv-if=\"indicatorMode === 'line'\"\n\t\t\t:class=\"[`u-swiper-indicator__wrapper--${indicatorMode}`]\"\n\t\t\t:style=\"{\n\t\t\t\twidth: $u.addUnit(lineWidth * length),\n\t\t\t\tbackgroundColor: indicatorInactiveColor\n\t\t\t}\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-swiper-indicator__wrapper--line__bar\"\n\t\t\t\t:style=\"[lineStyle]\"\n\t\t\t></view>\n\t\t</view>\n\t\t<view\n\t\t\tclass=\"u-swiper-indicator__wrapper\"\n\t\t\tv-if=\"indicatorMode === 'dot'\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-swiper-indicator__wrapper__dot\"\n\t\t\t\tv-for=\"(item, index) in length\"\n\t\t\t\t:key=\"index\"\n\t\t\t\t:class=\"[index === current && 'u-swiper-indicator__wrapper__dot--active']\"\n\t\t\t\t:style=\"[dotStyle(index)]\"\n\t\t\t>\n\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * SwiperIndicator 轮播图指示器\n\t * @description 该组件一般用于导航轮播，广告展示等场景,可开箱即用，\n\t * @tutorial https://www.uviewui.com/components/swiper.html\n\t * @property {String | Number}\tlength\t\t\t\t\t轮播的长度（默认 0 ）\n\t * @property {String | Number}\tcurrent\t\t\t\t\t当前处于活动状态的轮播的索引（默认 0 ）\n\t * @property {String}\t\t\tindicatorActiveColor\t指示器非激活颜色\n\t * @property {String}\t\t\tindicatorInactiveColor\t指示器的激活颜色\n\t * @property {String}\t\t\tindicatorMode\t\t\t指示器模式（默认 'line' ）\n\t * @example\t<u-swiper :list=\"list4\" indicator keyName=\"url\" :autoplay=\"false\"></u-swiper>\n\t */\n\texport default {\n\t\tname: 'u-swiper-indicator',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tlineWidth: 22\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 指示器为线型的样式\n\t\t\tlineStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\tstyle.width = uni.$u.addUnit(this.lineWidth)\n\t\t\t\tstyle.transform = `translateX(${ uni.$u.addUnit(this.current * this.lineWidth) })`\n\t\t\t\tstyle.backgroundColor = this.indicatorActiveColor\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// 指示器为点型的样式\n\t\t\tdotStyle() {\n\t\t\t\treturn index => {\n\t\t\t\t\tlet style = {}\n\t\t\t\t\tstyle.backgroundColor = index === this.current ? this.indicatorActiveColor : this.indicatorInactiveColor\n\t\t\t\t\treturn style\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-swiper-indicator {\n\n\t\t&__wrapper {\n\t\t\t@include flex;\n\n\t\t\t&--line {\n\t\t\t\tborder-radius: 100px;\n\t\t\t\theight: 4px;\n\n\t\t\t\t&__bar {\n\t\t\t\t\twidth: 22px;\n\t\t\t\t\theight: 4px;\n\t\t\t\t\tborder-radius: 100px;\n\t\t\t\t\tbackground-color: #FFFFFF;\n\t\t\t\t\ttransition: transform 0.3s;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t&__dot {\n\t\t\t\twidth: 5px;\n\t\t\t\theight: 5px;\n\t\t\t\tborder-radius: 100px;\n\t\t\t\tmargin: 0 4px;\n\n\t\t\t\t&--active {\n\t\t\t\t\twidth: 12px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-switch/props.js",
    "content": "export default {\n    props: {\n        // 是否为加载中状态\n        loading: {\n            type: Boolean,\n            default: uni.$u.props.switch.loading\n        },\n        // 是否为禁用装填\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.switch.disabled\n        },\n        // 开关尺寸，单位px\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.switch.size\n        },\n        // 打开时的背景颜色\n        activeColor: {\n            type: String,\n            default: uni.$u.props.switch.activeColor\n        },\n        // 关闭时的背景颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.switch.inactiveColor\n        },\n        // 通过v-model双向绑定的值\n        value: {\n            type: [Boolean, String, Number],\n            default: uni.$u.props.switch.value\n        },\n        // switch打开时的值\n        activeValue: {\n            type: [String, Number, Boolean],\n            default: uni.$u.props.switch.activeValue\n        },\n        // switch关闭时的值\n        inactiveValue: {\n            type: [String, Number, Boolean],\n            default: uni.$u.props.switch.inactiveValue\n        },\n        // 是否开启异步变更，开启后需要手动控制输入值\n        asyncChange: {\n            type: Boolean,\n            default: uni.$u.props.switch.asyncChange\n        },\n        // 圆点与外边框的距离\n        space: {\n            type: [String, Number],\n            default: uni.$u.props.switch.space\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-switch/u-switch.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-switch\"\n\t    :class=\"[disabled && 'u-switch--disabled']\"\n\t    :style=\"[switchStyle, $u.addStyle(customStyle)]\"\n\t    @tap=\"clickHandler\"\n\t>\n\t\t<view\n\t\t    class=\"u-switch__bg\"\n\t\t    :style=\"[bgStyle]\"\n\t\t>\n\t\t</view>\n\t\t<view\n\t\t    class=\"u-switch__node\"\n\t\t    :class=\"[value && 'u-switch__node--on']\"\n\t\t    :style=\"[nodeStyle]\"\n\t\t    ref=\"u-switch__node\"\n\t\t>\n\t\t\t<u-loading-icon\n\t\t\t    :show=\"loading\"\n\t\t\t    mode=\"circle\"\n\t\t\t    timingFunction='linear'\n\t\t\t    :color=\"value ? activeColor : '#AAABAD'\"\n\t\t\t    :size=\"size * 0.6\"\n\t\t\t/>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * switch 开关选择器\n\t * @description 选择开关一般用于只有两个选择，且只能选其一的场景。\n\t * @tutorial https://www.uviewui.com/components/switch.html\n\t * @property {Boolean}\t\t\t\t\t\tloading\t\t\t是否处于加载中（默认 false ）\n\t * @property {Boolean}\t\t\t\t\t\tdisabled\t\t是否禁用（默认 false ）\n\t * @property {String | Number}\t\t\t\tsize\t\t\t开关尺寸，单位px （默认 25 ）\n\t * @property {String}\t\t\t\t\t\tactiveColor\t\t打开时的背景色 （默认 '#2979ff' ）\n\t * @property {String} \t\t\t\t\t\tinactiveColor\t关闭时的背景色 （默认 '#ffffff' ）\n\t * @property {Boolean | String | Number}\tvalue\t\t\t通过v-model双向绑定的值 （默认 false ）\n\t * @property {Boolean | String | Number}\tactiveValue\t\t打开选择器时通过change事件发出的值 （默认 true ）\n\t * @property {Boolean | String | Number}\tinactiveValue\t关闭选择器时通过change事件发出的值 （默认 false ）\n\t * @property {Boolean}\t\t\t\t\t\tasyncChange\t\t是否开启异步变更，开启后需要手动控制输入值 （默认 false ）\n\t * @property {String | Number}\t\t\t\tspace\t\t\t圆点与外边框的距离 （默认 0 ）\n\t * @property {Object}\t\t\t\t\t\tcustomStyle\t\t定义需要用到的外部样式\n\t *\n\t * @event {Function} change 在switch打开或关闭时触发\n\t * @example <u-switch v-model=\"checked\" active-color=\"red\" inactive-color=\"#eee\"></u-switch>\n\t */\n\texport default {\n\t\tname: \"u-switch\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\twatch: {\n\t\t\tvalue: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler(n) {\n\t\t\t\t\tif(n !== this.inactiveValue && n !== this.activeValue) {\n\t\t\t\t\t\tuni.$u.error('v-model绑定的值必须为inactiveValue、activeValue二者之一')\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tbgColor: '#ffffff'\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tisActive(){\n\t\t\t\treturn this.value === this.activeValue;\n\t\t\t},\n\t\t\tswitchStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\t// 这里需要加2，是为了腾出边框的距离，否则圆点node会和外边框紧贴在一起\n\t\t\t\tstyle.width = uni.$u.addUnit(this.size * 2 + 2)\n\t\t\t\tstyle.height = uni.$u.addUnit(Number(this.size) + 2)\n\t\t\t\t// style.borderColor = this.value ? 'rgba(0, 0, 0, 0)' : 'rgba(0, 0, 0, 0.12)'\n\t\t\t\t// 如果自定义了“非激活”演示，name边框颜色设置为透明(跟非激活颜色一致)\n\t\t\t\t// 这里不能简单的设置为非激活的颜色，否则打开状态时，会有边框，所以需要透明\n\t\t\t\tif(this.customInactiveColor) {\n\t\t\t\t\tstyle.borderColor = 'rgba(0, 0, 0, 0)'\n\t\t\t\t}\n\t\t\t\tstyle.backgroundColor = this.isActive ? this.activeColor : this.inactiveColor\n\t\t\t\treturn style;\n\t\t\t},\n\t\t\tnodeStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\t// 如果自定义非激活颜色，将node圆点的尺寸减少两个像素，让其与外边框距离更大一点\n\t\t\t\tstyle.width = uni.$u.addUnit(this.size - this.space)\n\t\t\t\tstyle.height = uni.$u.addUnit(this.size - this.space)\n\t\t\t\tconst translateX = this.isActive ? uni.$u.addUnit(this.space) : uni.$u.addUnit(this.size);\n\t\t\t\tstyle.transform = `translateX(-${translateX})`\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tbgStyle() {\n\t\t\t\tlet style = {}\n\t\t\t\t// 这里配置一个多余的元素在HTML中，是为了让switch切换时，有更良好的背景色扩充体验(见实际效果)\n\t\t\t\tstyle.width = uni.$u.addUnit(Number(this.size) * 2 - this.size / 2)\n\t\t\t\tstyle.height = uni.$u.addUnit(this.size)\n\t\t\t\tstyle.backgroundColor = this.inactiveColor\n\t\t\t\t// 打开时，让此元素收缩，否则反之\n\t\t\t\tstyle.transform = `scale(${this.isActive ? 0 : 1})`\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tcustomInactiveColor() {\n\t\t\t\t// 之所以需要判断是否自定义了“非激活”颜色，是为了让node圆点离外边框更宽一点的距离\n\t\t\t\treturn this.inactiveColor !== '#fff' && this.inactiveColor !== '#ffffff'\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\tclickHandler() {\n\t\t\t\tif (!this.disabled && !this.loading) {\n\t\t\t\t\tconst oldValue = this.isActive ? this.inactiveValue : this.activeValue\n\t\t\t\t\tif(!this.asyncChange) {\n\t\t\t\t\t\tthis.$emit('input', oldValue)\n\t\t\t\t\t}\n\t\t\t\t\t// 放到下一个生命周期，因为双向绑定的value修改父组件状态需要时间，且是异步的\n\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\tthis.$emit('change', oldValue)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-switch {\n\t\t@include flex(row);\n\t\t/* #ifndef APP-NVUE */\n\t\tbox-sizing: border-box;\n\t\t/* #endif */\n\t\tposition: relative;\n\t\tbackground-color: #fff;\n\t\tborder-width: 1px;\n\t\tborder-radius: 100px;\n\t\ttransition: background-color 0.4s;\n\t\tborder-color: rgba(0, 0, 0, 0.12);\n\t\tborder-style: solid;\n\t\tjustify-content: flex-end;\n\t\talign-items: center;\n\t\t// 由于weex为阿里逗着玩的KPI项目，导致bug奇多，这必须要写这一行，\n\t\t// 否则在iOS上，点击页面任意地方，都会触发switch的点击事件\n\t\toverflow: hidden;\n\n\t\t&__node {\n\t\t\t@include flex(row);\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tborder-radius: 100px;\n\t\t\tbackground-color: #fff;\n\t\t\tborder-radius: 100px;\n\t\t\tbox-shadow: 1px 1px 1px 0 rgba(0, 0, 0, 0.25);\n\t\t\ttransition-property: transform;\n\t\t\ttransition-duration: 0.4s;\n\t\t\ttransition-timing-function: cubic-bezier(0.3, 1.05, 0.4, 1.05);\n\t\t}\n\n\t\t&__bg {\n\t\t\tposition: absolute;\n\t\t\tborder-radius: 100px;\n\t\t\tbackground-color: #FFFFFF;\n\t\t\ttransition-property: transform;\n\t\t\ttransition-duration: 0.4s;\n\t\t\tborder-top-left-radius: 0;\n\t\t\tborder-bottom-left-radius: 0;\n\t\t\ttransition-timing-function: ease;\n\t\t}\n\n\t\t&--disabled {\n\t\t\topacity: 0.6;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tabbar/props.js",
    "content": "export default {\n    props: {\n        // 当前匹配项的name\n        value: {\n            type: [String, Number, null],\n            default: uni.$u.props.tabbar.value\n        },\n        // 是否为iPhoneX留出底部安全距离\n        safeAreaInsetBottom: {\n            type: Boolean,\n            default: uni.$u.props.tabbar.safeAreaInsetBottom\n        },\n        // 是否显示上方边框\n        border: {\n            type: Boolean,\n            default: uni.$u.props.tabbar.border\n        },\n        // 元素层级z-index\n        zIndex: {\n            type: [String, Number],\n            default: uni.$u.props.tabbar.zIndex\n        },\n        // 选中标签的颜色\n        activeColor: {\n            type: String,\n            default: uni.$u.props.tabbar.activeColor\n        },\n        // 未选中标签的颜色\n        inactiveColor: {\n            type: String,\n            default: uni.$u.props.tabbar.inactiveColor\n        },\n        // 是否固定在底部\n        fixed: {\n            type: Boolean,\n            default: uni.$u.props.tabbar.fixed\n        },\n        // fixed定位固定在底部时，是否生成一个等高元素防止塌陷\n        placeholder: {\n            type: Boolean,\n            default: uni.$u.props.tabbar.placeholder\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tabbar/u-tabbar.vue",
    "content": "<template>\n\t<view class=\"u-tabbar\">\n\t\t<view\n\t\t    class=\"u-tabbar__content\"\n\t\t    ref=\"u-tabbar__content\"\n\t\t    @touchmove.stop.prevent=\"noop\"\n\t\t    :class=\"[border && 'u-border-top', fixed && 'u-tabbar--fixed']\"\n\t\t    :style=\"[tabbarStyle]\"\n\t\t>\n\t\t\t<view class=\"u-tabbar__content__item-wrapper\">\n\t\t\t\t<slot />\n\t\t\t</view>\n\t\t\t<u-safe-bottom v-if=\"safeAreaInsetBottom\"></u-safe-bottom>\n\t\t</view>\n\t\t<view\n\t\t    class=\"u-tabbar__placeholder\"\n\t\t\tv-if=\"placeholder\"\n\t\t    :style=\"{\n\t\t\t\theight: placeholderHeight + 'px',\n\t\t\t}\"\n\t\t></view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t/**\n\t * Tabbar 底部导航栏\n\t * @description 此组件提供了自定义tabbar的能力。\n\t * @tutorial https://www.uviewui.com/components/tabbar.html\n\t * @property {String | Number}\tvalue\t\t\t\t当前匹配项的name\n\t * @property {Boolean}\t\t\tsafeAreaInsetBottom\t是否为iPhoneX留出底部安全距离（默认 true ）\n\t * @property {Boolean}\t\t\tborder\t\t\t\t是否显示上方边框（默认 true ）\n\t * @property {String | Number}\tzIndex\t\t\t\t元素层级z-index（默认 1 ）\n\t * @property {String}\t\t\tactiveColor\t\t\t选中标签的颜色（默认 '#1989fa' ）\n\t * @property {String}\t\t\tinactiveColor\t\t未选中标签的颜色（默认 '#7d7e80' ）\n\t * @property {Boolean}\t\t\tfixed\t\t\t\t是否固定在底部（默认 true ）\n\t * @property {Boolean}\t\t\tplaceholder\t\t\tfixed定位固定在底部时，是否生成一个等高元素防止塌陷（默认 true ）\n\t * @property {Object}\t\t\tcustomStyle\t\t\t定义需要用到的外部样式\n\t * \n\t * @example <u-tabbar :value=\"value2\" :placeholder=\"false\" @change=\"name => value2 = name\" :fixed=\"false\" :safeAreaInsetBottom=\"false\"><u-tabbar-item text=\"首页\" icon=\"home\" dot ></u-tabbar-item></u-tabbar>\n\t */\n\texport default {\n\t\tname: 'u-tabbar',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tplaceholderHeight: 0\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\ttabbarStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tzIndex: this.zIndex\n\t\t\t\t}\n\t\t\t\t// 合并来自父组件的customStyle样式\n\t\t\t\treturn uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n\t\t\t},\n\t\t\t// 监听多个参数的变化，通过在computed执行对应的操作\n\t\t\tupdateChild() {\n\t\t\t\treturn [this.value, this.activeColor, this.inactiveColor]\n\t\t\t},\n\t\t\tupdatePlaceholder() {\n\t\t\t\treturn [this.fixed, this.placeholder]\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tupdateChild() {\n\t\t\t\t// 如果updateChildren中的元素发生了变化，则执行子元素初始化操作\n\t\t\t\tthis.updateChildren()\n\t\t\t},\n\t\t\tupdatePlaceholder() {\n\t\t\t\t// 如果fixed，placeholder等参数发生变化，重新计算占位元素的高度\n\t\t\t\tthis.setPlaceholderHeight()\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.children = []\n\t\t},\n\t\tmounted() {\n\t\t\tthis.setPlaceholderHeight()\n\t\t},\n\t\tmethods: {\n\t\t\tupdateChildren() {\n\t\t\t\t// 如果存在子元素，则执行子元素的updateFromParent进行更新数据\n\t\t\t\tthis.children.length && this.children.map(child => child.updateFromParent())\n\t\t\t},\n\t\t\t// 设置用于防止塌陷元素的高度\n\t\t\tasync setPlaceholderHeight() {\n\t\t\t\tif (!this.fixed || !this.placeholder) return\n\t\t\t\t// 延时一定时间\n\t\t\t\tawait uni.$u.sleep(20)\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tthis.$uGetRect('.u-tabbar__content').then(({height = 50}) => {\n\t\t\t\t\t// 修复IOS safearea bottom 未填充高度\n\t\t\t\t\tthis.placeholderHeight = height\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tdom.getComponentRect(this.$refs['u-tabbar__content'], (res) => {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tsize\n\t\t\t\t\t} = res\n\t\t\t\t\tthis.placeholderHeight = size.height\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-tabbar {\n\t\t@include flex(column);\n\t\tflex: 1;\n\t\tjustify-content: center;\n\t\t\n\t\t&__content {\n\t\t\t@include flex(column);\n\t\t\tbackground-color: #fff;\n\t\t\t\n\t\t\t&__item-wrapper {\n\t\t\t\theight: 50px;\n\t\t\t\t@include flex(row);\n\t\t\t}\n\t\t}\n\n\t\t&--fixed {\n\t\t\tposition: fixed;\n\t\t\tbottom: 0;\n\t\t\tleft: 0;\n\t\t\tright: 0;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tabbar-item/props.js",
    "content": "export default {\n    props: {\n        // item标签的名称，作为与u-tabbar的value参数匹配的标识符\n        name: {\n            type: [String, Number, null],\n            default: uni.$u.props.tabbarItem.name\n        },\n        // uView内置图标或者绝对路径的图片\n        icon: {\n            icon: String,\n            default: uni.$u.props.tabbarItem.icon\n        },\n        // 右上角的角标提示信息\n        badge: {\n            type: [String, Number, null],\n            default: uni.$u.props.tabbarItem.badge\n        },\n        // 是否显示圆点，将会覆盖badge参数\n        dot: {\n            type: Boolean,\n            default: uni.$u.props.tabbarItem.dot\n        },\n        // 描述文本\n        text: {\n            type: String,\n            default: uni.$u.props.tabbarItem.text\n        },\n        // 控制徽标的位置，对象或者字符串形式，可以设置top和right属性\n        badgeStyle: {\n            type: [Object, String],\n            default: uni.$u.props.tabbarItem.badgeStyle\n        }\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tabbar-item/u-tabbar-item.vue",
    "content": "<template>\n\t<view\n\t    class=\"u-tabbar-item\"\n\t    :style=\"[$u.addStyle(customStyle)]\"\n\t    @tap=\"clickHandler\"\n\t>\n\t\t<view class=\"u-tabbar-item__icon\">\n\t\t\t<u-icon\n\t\t\t    v-if=\"icon\"\n\t\t\t    :name=\"icon\"\n\t\t\t    :color=\"isActive? parentData.activeColor : parentData.inactiveColor\"\n\t\t\t    :size=\"20\"\n\t\t\t></u-icon>\n\t\t\t<template v-else>\n\t\t\t\t<slot\n\t\t\t\t    v-if=\"isActive\"\n\t\t\t\t    name=\"active-icon\"\n\t\t\t\t/>\n\t\t\t\t<slot\n\t\t\t\t    v-else\n\t\t\t\t    name=\"inactive-icon\"\n\t\t\t\t/>\n\t\t\t</template>\n\t\t\t<u-badge\n\t\t\t\tabsolute\n\t\t\t\t:offset=\"[0, dot ? '34rpx' : badge > 9 ? '14rpx' : '20rpx']\"\n\t\t\t    :customStyle=\"badgeStyle\"\n\t\t\t    :isDot=\"dot\"\n\t\t\t    :value=\"badge || (dot ? 1 : null)\"\n\t\t\t    :show=\"dot || badge > 0\"\n\t\t\t></u-badge>\n\t\t</view>\n\t\t\n\t\t<slot name=\"text\">\n\t\t\t<text\n\t\t\t    class=\"u-tabbar-item__text\"\n\t\t\t    :style=\"{\n\t\t\t\t\tcolor: isActive? parentData.activeColor : parentData.inactiveColor\n\t\t\t\t}\"\n\t\t\t>{{ text }}</text>\n\t\t</slot>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * TabbarItem 底部导航栏子组件\n\t * @description 此组件提供了自定义tabbar的能力。\n\t * @tutorial https://www.uviewui.com/components/tabbar.html\n\t * @property {String | Number}\tname\t\titem标签的名称，作为与u-tabbar的value参数匹配的标识符\n\t * @property {String}\t\t\ticon\t\tuView内置图标或者绝对路径的图片\n\t * @property {String | Number}\tbadge\t\t右上角的角标提示信息\n\t * @property {Boolean}\t\t\tdot\t\t\t是否显示圆点，将会覆盖badge参数（默认 false ）\n\t * @property {String}\t\t\ttext\t\t描述文本\n\t * @property {Object | String}\tbadgeStyle\t控制徽标的位置，对象或者字符串形式，可以设置top和right属性（默认 'top: 6px;right:2px;' ）\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * \n\t * @example <u-tabbar :value=\"value2\" :placeholder=\"false\" @change=\"name => value2 = name\" :fixed=\"false\" :safeAreaInsetBottom=\"false\"><u-tabbar-item text=\"首页\" icon=\"home\" dot ></u-tabbar-item></u-tabbar>\n\t */\n\texport default {\n\t\tname: 'u-tabbar-item',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisActive: false, // 是否处于激活状态\n\t\t\t\tparentData: {\n\t\t\t\t\tvalue: null,\n\t\t\t\t\tactiveColor: '',\n\t\t\t\t\tinactiveColor: ''\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\t// 支付宝小程序不支持provide/inject，所以使用这个方法获取整个父组件，在created定义，避免循环引用\n\t\t\t\tthis.updateParentData()\n\t\t\t\tif (!this.parent) {\n\t\t\t\t\tuni.$u.error('u-tabbar-item必须搭配u-tabbar组件使用')\n\t\t\t\t}\n\t\t\t\t// 本子组件在u-tabbar的children数组中的索引\n\t\t\t\tconst index = this.parent.children.indexOf(this)\n\t\t\t\t// 判断本组件的name(如果没有定义name，就用index索引)是否等于父组件的value参数\n\t\t\t\tthis.isActive = (this.name || index) === this.parentData.value\n\t\t\t},\n\t\t\tupdateParentData() {\n\t\t\t\t// 此方法在mixin中\n\t\t\t\tthis.getParentData('u-tabbar')\n\t\t\t},\n\t\t\t// 此方法将会被父组件u-tabbar调用\n\t\t\tupdateFromParent() {\n\t\t\t\t// 重新初始化\n\t\t\t\tthis.init()\n\t\t\t},\n\t\t\tclickHandler() {\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tconst index = this.parent.children.indexOf(this)\n\t\t\t\t\tconst name = this.name || index\n\t\t\t\t\t// 点击的item为非激活的item才发出change事件\n\t\t\t\t\tif (name !== this.parent.value) {\n\t\t\t\t\t\tthis.parent.$emit('change', name)\n\t\t\t\t\t}\n\t\t\t\t\tthis.$emit('click', name)\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-tabbar-item {\n\t\t@include flex(column);\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tflex: 1;\n\t\t\n\t\t&__icon {\n\t\t\t@include flex;\n\t\t\tposition: relative;\n\t\t\twidth: 150rpx;\n\t\t\tjustify-content: center;\n\t\t}\n\n\t\t&__text {\n\t\t\tmargin-top: 2px;\n\t\t\tfont-size: 12px;\n\t\t\tcolor: $u-content-color;\n\t\t}\n\t}\n\n\t/* #ifdef MP */\n\t// 由于小程序都使用shadow DOM形式实现，需要给影子宿主设置flex: 1才能让其撑开\n\t:host {\n\t\tflex: 1\n\t}\n\t/* #endif */\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-table/props.js",
    "content": "export default {\n    props: {\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-table/u-table.vue",
    "content": "<template>\n\t<view class=\"u-table\">\n\t\t\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Table 表格 \n\t * @description 表格组件一般用于展示大量结构化数据的场景 本组件标签类似HTML的table表格，由table、tr、th、td四个组件组成\n\t * @tutorial https://www.uviewui.com/components/table.html\n\t * @example <u-table><u-tr><u-th>学校</u-th </u-tr> <u-tr><u-td>浙江大学</u-td> </u-tr> <u-tr><u-td>清华大学</u-td> </u-tr></u-table>\n\t */\n\texport default {\n\t\tname: 'u-table',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\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=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tabs/props.js",
    "content": "export default {\n    props: {\n        // 滑块的移动过渡时间，单位ms\n        duration: {\n            type: Number,\n            default: uni.$u.props.tabs.duration\n        },\n        // tabs标签数组\n        list: {\n            type: Array,\n            default: uni.$u.props.tabs.list\n        },\n        // 滑块颜色\n        lineColor: {\n            type: String,\n            default: uni.$u.props.tabs.lineColor\n        },\n        // 菜单选择中时的样式\n        activeStyle: {\n            type: [String, Object],\n            default: uni.$u.props.tabs.activeStyle\n        },\n        // 菜单非选中时的样式\n        inactiveStyle: {\n            type: [String, Object],\n            default: uni.$u.props.tabs.inactiveStyle\n        },\n        // 滑块长度\n        lineWidth: {\n            type: [String, Number],\n            default: uni.$u.props.tabs.lineWidth\n        },\n        // 滑块高度\n        lineHeight: {\n            type: [String, Number],\n            default: uni.$u.props.tabs.lineHeight\n        },\n        // 滑块背景显示大小，当滑块背景设置为图片时使用\n        lineBgSize: {\n            type: String,\n            default: uni.$u.props.tabs.lineBgSize\n        },\n        // 菜单item的样式\n        itemStyle: {\n            type: [String, Object],\n            default: uni.$u.props.tabs.itemStyle\n        },\n        // 菜单是否可滚动\n        scrollable: {\n            type: Boolean,\n            default: uni.$u.props.tabs.scrollable\n        },\n\t\t// 当前选中标签的索引\n\t\tcurrent: {\n\t\t\ttype: [Number, String],\n\t\t\tdefault: uni.$u.props.tabs.current\n\t\t},\n\t\t// 默认读取的键名\n\t\tkeyName: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.tabs.keyName\n\t\t}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tabs/u-tabs.vue",
    "content": "<template>\n\t<view class=\"u-tabs\">\n\t\t<view class=\"u-tabs__wrapper\">\n\t\t\t<slot name=\"left\" />\n\t\t\t<view class=\"u-tabs__wrapper__scroll-view-wrapper\">\n\t\t\t\t<scroll-view\n\t\t\t\t\t:scroll-x=\"scrollable\"\n\t\t\t\t\t:scroll-left=\"scrollLeft\"\n\t\t\t\t\tscroll-with-animation\n\t\t\t\t\tclass=\"u-tabs__wrapper__scroll-view\"\n\t\t\t\t\t:show-scrollbar=\"false\"\n\t\t\t\t\tref=\"u-tabs__wrapper__scroll-view\"\n\t\t\t\t>\n\t\t\t\t\t<view\n\t\t\t\t\t\tclass=\"u-tabs__wrapper__nav\"\n\t\t\t\t\t\tref=\"u-tabs__wrapper__nav\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<view\n\t\t\t\t\t\t\tclass=\"u-tabs__wrapper__nav__item\"\n\t\t\t\t\t\t\tv-for=\"(item, index) in list\"\n\t\t\t\t\t\t\t:key=\"index\"\n\t\t\t\t\t\t\t@tap=\"clickHandler(item, index)\"\n\t\t\t\t\t\t\t:ref=\"`u-tabs__wrapper__nav__item-${index}`\"\n\t\t\t\t\t\t\t:style=\"[$u.addStyle(itemStyle), {flex: scrollable ? '' : 1}]\"\n\t\t\t\t\t\t\t:class=\"[`u-tabs__wrapper__nav__item-${index}`, item.disabled && 'u-tabs__wrapper__nav__item--disabled']\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\t:class=\"[item.disabled && 'u-tabs__wrapper__nav__item__text--disabled']\"\n\t\t\t\t\t\t\t\tclass=\"u-tabs__wrapper__nav__item__text\"\n\t\t\t\t\t\t\t\t:style=\"[textStyle(index)]\"\n\t\t\t\t\t\t\t>{{ item[keyName] }}</text>\n\t\t\t\t\t\t\t<u-badge\n\t\t\t\t\t\t\t\t:show=\"!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))\"\n\t\t\t\t\t\t\t\t:isDot=\"item.badge && item.badge.isDot || propsBadge.isDot\"\n\t\t\t\t\t\t\t\t:value=\"item.badge && item.badge.value || propsBadge.value\"\n\t\t\t\t\t\t\t\t:max=\"item.badge && item.badge.max || propsBadge.max\"\n\t\t\t\t\t\t\t\t:type=\"item.badge && item.badge.type || propsBadge.type\"\n\t\t\t\t\t\t\t\t:showZero=\"item.badge && item.badge.showZero || propsBadge.showZero\"\n\t\t\t\t\t\t\t\t:bgColor=\"item.badge && item.badge.bgColor || propsBadge.bgColor\"\n\t\t\t\t\t\t\t\t:color=\"item.badge && item.badge.color || propsBadge.color\"\n\t\t\t\t\t\t\t\t:shape=\"item.badge && item.badge.shape || propsBadge.shape\"\n\t\t\t\t\t\t\t\t:numberType=\"item.badge && item.badge.numberType || propsBadge.numberType\"\n\t\t\t\t\t\t\t\t:inverted=\"item.badge && item.badge.inverted || propsBadge.inverted\"\n\t\t\t\t\t\t\t\tcustomStyle=\"margin-left: 4px;\"\n\t\t\t\t\t\t\t></u-badge>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<!-- #ifdef APP-NVUE -->\n\t\t\t\t\t\t<view\n\t\t\t\t\t\t\tclass=\"u-tabs__wrapper__nav__line\"\n\t\t\t\t\t\t\tref=\"u-tabs__wrapper__nav__line\"\n\t\t\t\t\t\t\t:style=\"[{\n\t\t\t\t\t\t\t\t\twidth: $u.addUnit(lineWidth),\n\t\t\t\t\t\t\t\t\theight: $u.addUnit(lineHeight),\n\t\t\t\t\t\t\t\t\tbackground: lineColor,\n\t\t\t\t\t\t\t\t\tbackgroundSize: lineBgSize,\n\t\t\t\t\t\t\t\t}]\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<!-- #endif -->\n\t\t\t\t\t\t\t<!-- #ifndef APP-NVUE -->\n\t\t\t\t\t\t\t<view\n\t\t\t\t\t\t\t\tclass=\"u-tabs__wrapper__nav__line\"\n\t\t\t\t\t\t\t\tref=\"u-tabs__wrapper__nav__line\"\n\t\t\t\t\t\t\t\t:style=\"[{\n\t\t\t\t\t\t\t\t\t\twidth: $u.addUnit(lineWidth),\n\t\t\t\t\t\t\t\t\t\ttransform: `translate(${lineOffsetLeft}px)`,\n\t\t\t\t\t\t\t\t\t\ttransitionDuration: `${firstTime ? 0 : duration}ms`,\n\t\t\t\t\t\t\t\t\t\theight: $u.addUnit(lineHeight),\n\t\t\t\t\t\t\t\t\t\tbackground: lineColor,\n\t\t\t\t\t\t\t\t\t\tbackgroundSize: lineBgSize,\n\t\t\t\t\t\t\t\t\t}]\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<!-- #endif -->\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t</scroll-view>\n\t\t\t</view>\n\t\t\t<slot name=\"right\" />\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\t// #ifdef APP-NVUE\n\tconst animation = uni.requireNativePlugin('animation')\n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\timport props from './props.js';\n\t/**\n\t * Tabs 标签\n\t * @description tabs标签组件，在标签多的时候，可以配置为左右滑动，标签少的时候，可以禁止滑动。 该组件的一个特点是配置为滚动模式时，激活的tab会自动移动到组件的中间位置。\n\t * @tutorial https://www.uviewui.com/components/tabs.html\n\t * @property {String | Number}\tduration\t\t\t滑块移动一次所需的时间，单位秒（默认 200 ）\n\t * @property {String | Number}\tswierWidth\t\t\tswiper的宽度（默认 '750rpx' ）\n\t * @property {String}\tkeyName\t 从`list`元素对象中读取的键名（默认 'name' ）\n\t * @event {Function(index)} change 标签改变时触发 index: 点击了第几个tab，索引从0开始\n\t * @event {Function(index)} click 点击标签时触发 index: 点击了第几个tab，索引从0开始\n\t * @example <u-tabs :list=\"list\" :is-scroll=\"false\" :current=\"current\" @change=\"change\"></u-tabs>\n\t */\n\texport default {\n\t\tname: 'u-tabs',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tfirstTime: true,\n\t\t\t\tscrollLeft: 0,\n\t\t\t\tscrollViewWidth: 0,\n\t\t\t\tlineOffsetLeft: 0,\n\t\t\t\ttabsRect: {\n\t\t\t\t\tleft: 0\n\t\t\t\t},\n\t\t\t\tinnerCurrent: 0,\n\t\t\t\tmoving: false,\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tcurrent: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler (newValue, oldValue) {\n\t\t\t\t\t// 内外部值不相等时，才尝试移动滑块\n\t\t\t\t\tif (newValue !== this.innerCurrent) {\n\t\t\t\t\t\tthis.innerCurrent = newValue\n\t\t\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\t\t\tthis.resize()\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// list变化时，重新渲染list各项信息\n\t\t\tlist() {\n\t\t\t\tthis.$nextTick(() => {\n\t\t\t\t\tthis.resize()\n\t\t\t\t})\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\ttextStyle() {\n\t\t\t\treturn index => {\n\t\t\t\t\tconst style = {}\n\t\t\t\t\t// 取当期是否激活的样式\n\t\t\t\t\tconst customeStyle = index === this.innerCurrent ? uni.$u.addStyle(this.activeStyle) : uni.$u\n\t\t\t\t\t\t.addStyle(\n\t\t\t\t\t\t\tthis.inactiveStyle)\n\t\t\t\t\t// 如果当前菜单被禁用，则加上对应颜色，需要在此做处理，是因为nvue下，无法在style样式中通过!import覆盖标签的内联样式\n\t\t\t\t\tif (this.list[index].disabled) {\n\t\t\t\t\t\tstyle.color = '#c8c9cc'\n\t\t\t\t\t}\n\t\t\t\t\treturn uni.$u.deepMerge(customeStyle, style)\n\t\t\t\t}\n\t\t\t},\n\t\t\tpropsBadge() {\n\t\t\t\treturn uni.$u.props.badge\n\t\t\t}\n\t\t},\n\t\tasync mounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tsetLineLeft() {\n\t\t\t\tconst tabItem = this.list[this.innerCurrent];\n\t\t\t\tif (!tabItem) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// 获取滑块该移动的位置\n\t\t\t\tlet lineOffsetLeft = this.list\n\t\t\t\t\t.slice(0, this.innerCurrent)\n\t\t\t\t\t.reduce((total, curr) => total + curr.rect.width, 0);\n                // 获取下划线的数值px表示法\n\t\t\t\tconst lineWidth = uni.$u.getPx(this.lineWidth);\n\t\t\t\tthis.lineOffsetLeft = lineOffsetLeft + (tabItem.rect.width - lineWidth) / 2\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// 第一次移动滑块，无需过渡时间\n\t\t\t\tthis.animation(this.lineOffsetLeft, this.firstTime ? 0 : parseInt(this.duration))\n\t\t\t\t// #endif\n\n\t\t\t\t// 如果是第一次执行此方法，让滑块在初始化时，瞬间滑动到第一个tab item的中间\n\t\t\t\t// 这里需要一个定时器，因为在非nvue下，是直接通过style绑定过渡时间，需要等其过渡完成后，再设置为false(非第一次移动滑块)\n\t\t\t\tif (this.firstTime) {\n\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\tthis.firstTime = false\n\t\t\t\t\t}, 10);\n\t\t\t\t}\n\t\t\t},\n\t\t\t// nvue下设置滑块的位置\n\t\t\tanimation(x, duration = 0) {\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tconst ref = this.$refs['u-tabs__wrapper__nav__line']\n\t\t\t\tanimation.transition(ref, {\n\t\t\t\t\tstyles: {\n\t\t\t\t\t\ttransform: `translateX(${x}px)`\n\t\t\t\t\t},\n\t\t\t\t\tduration\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 点击某一个标签\n\t\t\tclickHandler(item, index) {\n\t\t\t\t// 因为标签可能为disabled状态，所以click是一定会发出的，但是change事件是需要可用的状态才发出\n\t\t\t\tthis.$emit('click', {\n\t\t\t\t\t...item,\n\t\t\t\t\tindex\n\t\t\t\t})\n\t\t\t\t// 如果disabled状态，返回\n\t\t\t\tif (item.disabled) return\n\t\t\t\tthis.innerCurrent = index\n\t\t\t\tthis.resize()\n\t\t\t\tthis.$emit('change', {\n\t\t\t\t\t...item,\n\t\t\t\t\tindex\n\t\t\t\t})\n\t\t\t},\n\t\t\tinit() {\n\t\t\t\tuni.$u.sleep().then(() => {\n\t\t\t\t\tthis.resize()\n\t\t\t\t})\n\t\t\t},\n\t\t\tsetScrollLeft() {\n\t\t\t\t// 当前活动tab的布局信息，有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息\n\t\t\t\tconst tabRect = this.list[this.innerCurrent]\n\t\t\t\t// 累加得到当前item到左边的距离\n\t\t\t\tconst offsetLeft = this.list\n\t\t\t\t\t.slice(0, this.innerCurrent)\n\t\t\t\t\t.reduce((total, curr) => {\n\t\t\t\t\t\treturn total + curr.rect.width\n\t\t\t\t\t}, 0)\n\t\t\t\t// 此处为屏幕宽度\n\t\t\t\tconst windowWidth = uni.$u.sys().windowWidth\n\t\t\t\t// 将活动的tabs-item移动到屏幕正中间，实际上是对scroll-view的移动\n\t\t\t\tlet scrollLeft = offsetLeft - (this.tabsRect.width - tabRect.rect.width) / 2 - (windowWidth - this.tabsRect\n\t\t\t\t\t.right) / 2 + this.tabsRect.left / 2\n\t\t\t\t// 这里做一个限制，限制scrollLeft的最大值为整个scroll-view宽度减去tabs组件的宽度\n\t\t\t\tscrollLeft = Math.min(scrollLeft, this.scrollViewWidth - this.tabsRect.width)\n\t\t\t\tthis.scrollLeft = Math.max(0, scrollLeft)\n\t\t\t},\n\t\t\t// 获取所有标签的尺寸\n\t\t\tresize() {\n\t\t\t\t// 如果不存在list，则不处理\n\t\t\t\tif(this.list.length === 0) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tPromise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => {\n\t\t\t\t\tthis.tabsRect = tabsRect\n\t\t\t\t\tthis.scrollViewWidth = 0\n\t\t\t\t\titemRect.map((item, index) => {\n\t\t\t\t\t\t// 计算scroll-view的宽度，这里\n\t\t\t\t\t\tthis.scrollViewWidth += item.width\n\t\t\t\t\t\t// 另外计算每一个item的中心点X轴坐标\n\t\t\t\t\t\tthis.list[index].rect = item\n\t\t\t\t\t})\n\t\t\t\t\t// 获取了tabs的尺寸之后，设置滑块的位置\n\t\t\t\t\tthis.setLineLeft()\n\t\t\t\t\tthis.setScrollLeft()\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 获取导航菜单的尺寸\n\t\t\tgetTabsRect() {\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tthis.queryRect('u-tabs__wrapper__scroll-view').then(size => resolve(size))\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 获取所有标签的尺寸\n\t\t\tgetAllItemRect() {\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tconst promiseAllArr = this.list.map((item, index) => this.queryRect(\n\t\t\t\t\t\t`u-tabs__wrapper__nav__item-${index}`, true))\n\t\t\t\t\tPromise.all(promiseAllArr).then(sizes => resolve(sizes))\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 获取各个标签的尺寸\n\t\t\tqueryRect(el, item) {\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t// $uGetRect为uView自带的节点查询简化方法，详见文档介绍：https://www.uviewui.com/js/getRect.html\n\t\t\t\t// 组件内部一般用this.$uGetRect，对外的为uni.$u.getRect，二者功能一致，名称不同\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tthis.$uGetRect(`.${el}`).then(size => {\n\t\t\t\t\t\tresolve(size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// nvue下，使用dom模块查询元素高度\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tdom.getComponentRect(item ? this.$refs[el][0] : this.$refs[el], res => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-tabs {\n\n\t\t&__wrapper {\n\t\t\t@include flex;\n\t\t\talign-items: center;\n\n\t\t\t&__scroll-view-wrapper {\n\t\t\t\tflex: 1;\n\t\t\t\t/* #ifndef APP-NVUE */\n\t\t\t\toverflow: auto hidden;\n\t\t\t\t/* #endif */\n\t\t\t}\n\n\t\t\t&__scroll-view {\n\t\t\t\t@include flex;\n\t\t\t\tflex: 1;\n\t\t\t}\n\n\t\t\t&__nav {\n\t\t\t\t@include flex;\n\t\t\t\tposition: relative;\n\n\t\t\t\t&__item {\n\t\t\t\t\tpadding: 0 11px;\n\t\t\t\t\t@include flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tjustify-content: center;\n\n\t\t\t\t\t&--disabled {\n\t\t\t\t\t\t/* #ifndef APP-NVUE */\n\t\t\t\t\t\tcursor: not-allowed;\n\t\t\t\t\t\t/* #endif */\n\t\t\t\t\t}\n\n\t\t\t\t\t&__text {\n\t\t\t\t\t\tfont-size: 15px;\n\t\t\t\t\t\tcolor: $u-content-color;\n\n\t\t\t\t\t\t&--disabled {\n\t\t\t\t\t\t\tcolor: $u-disabled-color !important;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&__line {\n\t\t\t\t\theight: 3px;\n\t\t\t\t\tbackground: $u-primary;\n\t\t\t\t\twidth: 30px;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tbottom: 2px;\n\t\t\t\t\tborder-radius: 100px;\n\t\t\t\t\ttransition-property: transform;\n\t\t\t\t\ttransition-duration: 300ms;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tabs-item/props.js",
    "content": "export default {\n    props: {\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tabs-item/u-tabs-item.vue",
    "content": "<template>\n\t<swiper-item>\n\t\t<slot />\n\t</swiper-item>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * TabsItem  tabs标签组件的自组件\n\t * @description tabs标签组件，在标签多的时候，可以配置为左右滑动，标签少的时候，可以禁止滑动。 该组件的一个特点是配置为滚动模式时，激活的tab会自动移动到组件的中间位置。\n\t * @tutorial https://www.uviewui.com/components/tabs.html\n\t * @property {type}\tprop_name\n\t * @event {Function()} \n\t * @example \n\t */\n\texport default {\n\t\tname: 'u-tabs-item',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\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>\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tag/props.js",
    "content": "export default {\n    props: {\n        // 标签类型info、primary、success、warning、error\n        type: {\n            type: String,\n            default: uni.$u.props.tag.type\n        },\n        // 不可用\n        disabled: {\n            type: [Boolean, String],\n            default: uni.$u.props.tag.disabled\n        },\n        // 标签的大小，large，medium，mini\n        size: {\n            type: String,\n            default: uni.$u.props.tag.size\n        },\n        // tag的形状，circle（两边半圆形）, square（方形，带圆角）\n        shape: {\n            type: String,\n            default: uni.$u.props.tag.shape\n        },\n        // 标签文字\n        text: {\n            type: [String, Number],\n            default: uni.$u.props.tag.text\n        },\n        // 背景颜色，默认为空字符串，即不处理\n        bgColor: {\n            type: String,\n            default: uni.$u.props.tag.bgColor\n        },\n        // 标签字体颜色，默认为空字符串，即不处理\n        color: {\n            type: String,\n            default: uni.$u.props.tag.color\n        },\n        // 标签的边框颜色\n        borderColor: {\n            type: String,\n            default: uni.$u.props.tag.borderColor\n        },\n        // 关闭按钮图标的颜色\n        closeColor: {\n            type: String,\n            default: uni.$u.props.tag.closeColor\n        },\n        // 点击时返回的索引值，用于区分例遍的数组哪个元素被点击了\n        name: {\n            type: [String, Number],\n            default: uni.$u.props.tag.name\n        },\n        // // 模式选择，dark|light|plain\n        // mode: {\n        // \ttype: String,\n        // \tdefault: 'light'\n        // },\n        // 镂空时是否填充背景色\n        plainFill: {\n            type: Boolean,\n            default: uni.$u.props.tag.plainFill\n        },\n        // 是否镂空\n        plain: {\n            type: Boolean,\n            default: uni.$u.props.tag.plain\n        },\n        // 是否可关闭\n        closable: {\n            type: Boolean,\n            default: uni.$u.props.tag.closable\n        },\n        // 是否显示\n        show: {\n            type: Boolean,\n            default: uni.$u.props.tag.show\n        },\n        // 内置图标，或绝对路径的图片\n        icon: {\n            type: String,\n            default: uni.$u.props.tag.icon\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tag/u-tag.vue",
    "content": "<template>\n\t<u-transition\n\t\tmode=\"fade\"\n\t\t:show=\"show\"\n\t>\n\t\t<view class=\"u-tag-wrapper\">\n\t\t\t<view\n\t\t\t\tclass=\"u-tag\"\n\t\t\t\t:class=\"[`u-tag--${shape}`, !plain && `u-tag--${type}`, plain && `u-tag--${type}--plain`, `u-tag--${size}`, plain && plainFill && `u-tag--${type}--plain--fill`]\"\n\t\t\t\t@tap.stop=\"clickHandler\"\n\t\t\t\t:style=\"[{\n\t\t\t\t\tmarginRight: closable ? '10px' : 0,\n\t\t\t\t\tmarginTop: closable ? '10px' : 0,\n\t\t\t\t}, style]\"\n\t\t\t>\n\t\t\t\t<slot name=\"icon\">\n\t\t\t\t\t<view\n\t\t\t\t\t\tclass=\"u-tag__icon\"\n\t\t\t\t\t\tv-if=\"icon\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<image\n\t\t\t\t\t\t\tv-if=\"$u.test.image(icon)\"\n\t\t\t\t\t\t\t:src=\"icon\"\n\t\t\t\t\t\t\t:style=\"[imgStyle]\"\n\t\t\t\t\t\t></image>\n\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\t:color=\"elIconColor\"\n\t\t\t\t\t\t\t:name=\"icon\"\n\t\t\t\t\t\t\t:size=\"iconSize\"\n\t\t\t\t\t\t></u-icon>\n\t\t\t\t\t</view>\n\t\t\t\t</slot>\n\t\t\t\t<text\n\t\t\t\t\tclass=\"u-tag__text\"\n\t\t\t\t\t:style=\"[textColor]\"\n\t\t\t\t\t:class=\"[`u-tag__text--${type}`, plain && `u-tag__text--${type}--plain`, `u-tag__text--${size}`]\"\n\t\t\t\t>{{ text }}</text>\n\t\t\t</view>\n\t\t\t<view\n\t\t\t\tclass=\"u-tag__close\"\n\t\t\t\t:class=\"[`u-tag__close--${size}`]\"\n\t\t\t\tv-if=\"closable\"\n\t\t\t\t@tap.stop=\"closeHandler\"\n\t\t\t\t:style=\"{backgroundColor: closeColor}\"\n\t\t\t>\n\t\t\t\t<u-icon\n\t\t\t\t\tname=\"close\"\n\t\t\t\t\t:size=\"closeSize\"\n\t\t\t\t\tcolor=\"#ffffff\"\n\t\t\t\t></u-icon>\n\t\t\t</view>\n\t\t</view>\n\t</u-transition>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Tag 标签\n\t * @description tag组件一般用于标记和选择，我们提供了更加丰富的表现形式，能够较全面的涵盖您的使用场景\n\t * @tutorial https://www.uviewui.com/components/tag.html\n\t * @property {String}\t\t\ttype\t\t标签类型info、primary、success、warning、error （默认 'primary' ）\n\t * @property {Boolean | String}\tdisabled\t不可用（默认 false ）\n\t * @property {String}\t\t\tsize\t\t标签的大小，large，medium，mini （默认 'medium' ）\n\t * @property {String}\t\t\tshape\t\ttag的形状，circle（两边半圆形）, square（方形，带圆角）（默认 'square' ）\n\t * @property {String | Number}\ttext\t\t标签的文字内容 \n\t * @property {String}\t\t\tbgColor\t\t背景颜色，默认为空字符串，即不处理\n\t * @property {String}\t\t\tcolor\t\t标签字体颜色，默认为空字符串，即不处理\n\t * @property {String}\t\t\tborderColor\t镂空形式标签的边框颜色\n\t * @property {String}\t\t\tcloseColor\t关闭按钮图标的颜色（默认 #C6C7CB）\n\t * @property {String | Number}\tname\t\t点击时返回的索引值，用于区分例遍的数组哪个元素被点击了\n\t * @property {Boolean}\t\t\tplainFill\t镂空时是否填充背景色（默认 false ）\n\t * @property {Boolean}\t\t\tplain\t\t是否镂空（默认 false ）\n\t * @property {Boolean}\t\t\tclosable\t是否可关闭，设置为true，文字右边会出现一个关闭图标（默认 false ）\n\t * @property {Boolean}\t\t\tshow\t\t标签显示与否（默认 true ）\n\t * @property {String}\t\t\ticon\t\t内置图标，或绝对路径的图片\n\t * @event {Function(index)} click 点击标签时触发 index: 传递的index参数值\n\t * @event {Function(index)} close closable为true时，点击标签关闭按钮触发 index: 传递的index参数值\t\n\t * @example <u-tag text=\"标签\" type=\"error\" plain plainFill></u-tag>\n\t */\n\texport default {\n\t\tname: 'u-tag',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\tstyle() {\n\t\t\t\tconst style = {}\n\t\t\t\tif (this.bgColor) {\n\t\t\t\t\tstyle.backgroundColor = this.bgColor\n\t\t\t\t}\n\t\t\t\tif (this.color) {\n\t\t\t\t\tstyle.color = this.color\n\t\t\t\t}\n\t\t\t\tif(this.borderColor) {\n\t\t\t\t\tstyle.borderColor = this.borderColor\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\t// nvue下，文本颜色无法继承父元素\n\t\t\ttextColor() {\n\t\t\t\tconst style = {}\n\t\t\t\tif (this.color) {\n\t\t\t\t\tstyle.color = this.color\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t},\n\t\t\timgStyle() {\n\t\t\t\tconst width = this.size === 'large' ? '17px' : this.size === 'medium' ? '15px' : '13px'\n\t\t\t\treturn {\n\t\t\t\t\twidth,\n\t\t\t\t\theight: width\n\t\t\t\t}\n\t\t\t},\n\t\t\t// 文本的样式\n\t\t\tcloseSize() {\n\t\t\t\tconst size = this.size === 'large' ? 15 : this.size === 'medium' ? 13 : 12\n\t\t\t\treturn size\n\t\t\t},\n\t\t\t// 图标大小\n\t\t\ticonSize() {\n\t\t\t\tconst size = this.size === 'large' ? 21 : this.size === 'medium' ? 19 : 16\n\t\t\t\treturn size\n\t\t\t},\n\t\t\t// 图标颜色\n\t\t\telIconColor() {\n\t\t\t\treturn this.iconColor ? this.iconColor : this.plain ? this.type : '#ffffff'\n\t\t\t}\n\t\t},\n\t\tmethods: {\n\t\t\t// 点击关闭按钮\n\t\t\tcloseHandler() {\n\t\t\t\tthis.$emit('close', this.name)\n\t\t\t},\n\t\t\t// 点击标签\n\t\t\tclickHandler() {\n\t\t\t\tthis.$emit('click', this.name)\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style\n\tlang=\"scss\"\n\tscoped\n>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-tag-wrapper {\n\t\tposition: relative;\n\t}\n\n\t.u-tag {\n\t\t@include flex;\n\t\talign-items: center;\n\t\tborder-style: solid;\n\n\t\t&--circle {\n\t\t\tborder-radius: 100px;\n\t\t}\n\n\t\t&--square {\n\t\t\tborder-radius: 3px;\n\t\t}\n\n\t\t&__icon {\n\t\t\tmargin-right: 4px;\n\t\t}\n\n\t\t&__text {\n\t\t\t&--mini {\n\t\t\t\tfont-size: 12px;\n\t\t\t\tline-height: 12px;\n\t\t\t}\n\n\t\t\t&--medium {\n\t\t\t\tfont-size: 13px;\n\t\t\t\tline-height: 13px;\n\t\t\t}\n\n\t\t\t&--large {\n\t\t\t\tfont-size: 15px;\n\t\t\t\tline-height: 15px;\n\t\t\t}\n\t\t}\n\n\t\t&--mini {\n\t\t\theight: 22px;\n\t\t\tline-height: 22px;\n\t\t\tpadding: 0 5px;\n\t\t}\n\n\t\t&--medium {\n\t\t\theight: 26px;\n\t\t\tline-height: 22px;\n\t\t\tpadding: 0 10px;\n\t\t}\n\n\t\t&--large {\n\t\t\theight: 32px;\n\t\t\tline-height: 32px;\n\t\t\tpadding: 0 15px;\n\t\t}\n\n\t\t&--primary {\n\t\t\tbackground-color: $u-primary;\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-primary;\n\t\t}\n\n\t\t&--primary--plain {\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-primary;\n\t\t}\n\n\t\t&--primary--plain--fill {\n\t\t\tbackground-color: #ecf5ff;\n\t\t}\n\n\t\t&__text--primary {\n\t\t\tcolor: #FFFFFF;\n\t\t}\n\n\t\t&__text--primary--plain {\n\t\t\tcolor: $u-primary;\n\t\t}\n\n\t\t&--error {\n\t\t\tbackground-color: $u-error;\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-error;\n\t\t}\n\n\t\t&--error--plain {\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-error;\n\t\t}\n\n\t\t&--error--plain--fill {\n\t\t\tbackground-color: #fef0f0;\n\t\t}\n\n\t\t&__text--error {\n\t\t\tcolor: #FFFFFF;\n\t\t}\n\n\t\t&__text--error--plain {\n\t\t\tcolor: $u-error;\n\t\t}\n\n\t\t&--warning {\n\t\t\tbackground-color: $u-warning;\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-warning;\n\t\t}\n\n\t\t&--warning--plain {\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-warning;\n\t\t}\n\n\t\t&--warning--plain--fill {\n\t\t\tbackground-color: #fdf6ec;\n\t\t}\n\n\t\t&__text--warning {\n\t\t\tcolor: #FFFFFF;\n\t\t}\n\n\t\t&__text--warning--plain {\n\t\t\tcolor: $u-warning;\n\t\t}\n\n\t\t&--success {\n\t\t\tbackground-color: $u-success;\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-success;\n\t\t}\n\n\t\t&--success--plain {\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-success;\n\t\t}\n\n\t\t&--success--plain--fill {\n\t\t\tbackground-color: #f5fff0;\n\t\t}\n\n\t\t&__text--success {\n\t\t\tcolor: #FFFFFF;\n\t\t}\n\n\t\t&__text--success--plain {\n\t\t\tcolor: $u-success;\n\t\t}\n\n\t\t&--info {\n\t\t\tbackground-color: $u-info;\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-info;\n\t\t}\n\n\t\t&--info--plain {\n\t\t\tborder-width: 1px;\n\t\t\tborder-color: $u-info;\n\t\t}\n\n\t\t&--info--plain--fill {\n\t\t\tbackground-color: #f4f4f5;\n\t\t}\n\n\t\t&__text--info {\n\t\t\tcolor: #FFFFFF;\n\t\t}\n\n\t\t&__text--info--plain {\n\t\t\tcolor: $u-info;\n\t\t}\n\n\t\t&__close {\n\t\t\tposition: absolute;\n\t\t\tz-index: 999;\n\t\t\ttop: 10px;\n\t\t\tright: 10px;\n\t\t\tborder-radius: 100px;\n\t\t\tbackground-color: #C6C7CB;\n\t\t\t@include flex(row);\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\ttransform: scale(0.6) translate(80%, -80%);\n\t\t\t/* #endif */\n\t\t\t/* #ifdef APP-NVUE */\n\t\t\ttransform: scale(0.6) translate(50%, -50%);\n\t\t\t/* #endif */\n\n\t\t\t&--mini {\n\t\t\t\twidth: 18px;\n\t\t\t\theight: 18px;\n\t\t\t}\n\n\t\t\t&--medium {\n\t\t\t\twidth: 22px;\n\t\t\t\theight: 22px;\n\t\t\t}\n\n\t\t\t&--large {\n\t\t\t\twidth: 25px;\n\t\t\t\theight: 25px;\n\t\t\t}\n\t\t}\n\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-td/props.js",
    "content": "export default {\n    props: {\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-td/u-td.vue",
    "content": "<template>\n\t<view class=\"u-td\">\n\t\t\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/** \n\t * Td 表格中的单元格\n\t * @description \n\t * @tutorial url\n\t * @property {String | Number} \n\t * @event {Function}\n\t * @example\n\t */\n\texport default {\n\t\tname: 'u-td',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\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=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-text/props.js",
    "content": "export default {\n    props: {\n        // 主题颜色\n        type: {\n            type: String,\n            default: uni.$u.props.text.type\n        },\n        // 是否显示\n        show: {\n            type: Boolean,\n            default: uni.$u.props.text.show\n        },\n        // 显示的值\n        text: {\n            type: [String, Number],\n            default: uni.$u.props.text.text\n        },\n        // 前置图标\n        prefixIcon: {\n            type: String,\n            default: uni.$u.props.text.prefixIcon\n        },\n        // 后置图标\n        suffixIcon: {\n            type: String,\n            default: uni.$u.props.text.suffixIcon\n        },\n        // 文本处理的匹配模式\n        // text-普通文本，price-价格，phone-手机号，name-姓名，date-日期，link-超链接\n        mode: {\n            type: String,\n            default: uni.$u.props.text.mode\n        },\n        // mode=link下，配置的链接\n        href: {\n            type: String,\n            default: uni.$u.props.text.href\n        },\n        // 格式化规则\n        format: {\n            type: [String, Function],\n            default: uni.$u.props.text.format\n        },\n        // mode=phone时，点击文本是否拨打电话\n        call: {\n            type: Boolean,\n            default: uni.$u.props.text.call\n        },\n        // 小程序的打开方式\n        openType: {\n            type: String,\n            default: uni.$u.props.text.openType\n        },\n        // 是否粗体，默认normal\n        bold: {\n            type: Boolean,\n            default: uni.$u.props.text.bold\n        },\n        // 是否块状\n        block: {\n            type: Boolean,\n            default: uni.$u.props.text.block\n        },\n        // 文本显示的行数，如果设置，超出此行数，将会显示省略号\n        lines: {\n            type: [String, Number],\n            default: uni.$u.props.text.lines\n        },\n        // 文本颜色\n        color: {\n            type: String,\n            default: uni.$u.props.text.color\n        },\n        // 字体大小\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.text.size\n        },\n        // 图标的样式\n        iconStyle: {\n            type: [Object, String],\n            default: uni.$u.props.text.iconStyle\n        },\n        // 文字装饰，下划线，中划线等，可选值 none|underline|line-through\n        decoration: {\n            tepe: String,\n            default: uni.$u.props.text.decoration\n        },\n        // 外边距，对象、字符串，数值形式均可\n        margin: {\n            type: [Object, String, Number],\n            default: uni.$u.props.text.margin\n        },\n        // 文本行高\n        lineHeight: {\n            type: [String, Number],\n            default: uni.$u.props.text.lineHeight\n        },\n        // 文本对齐方式，可选值left|center|right\n        align: {\n            type: String,\n            default: uni.$u.props.text.align\n        },\n        // 文字换行，可选值break-word|normal|anywhere\n        wordWrap: {\n            type: String,\n            default: uni.$u.props.text.wordWrap\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-text/u-text.vue",
    "content": "<template>\n    <view\n        class=\"u-text\"\n        :class=\"[]\"\n        v-if=\"show\"\n        :style=\"{\n            margin: margin,\n\t\t\tjustifyContent: align === 'left' ? 'flex-start' : align === 'center' ? 'center' : 'flex-end'\n        }\"\n        @tap=\"clickHandler\"\n    >\n        <text\n            :class=\"['u-text__price', type && `u-text__value--${type}`]\"\n            v-if=\"mode === 'price'\"\n            :style=\"[valueStyle]\"\n            >￥</text\n        >\n        <view class=\"u-text__prefix-icon\" v-if=\"prefixIcon\">\n            <u-icon\n                :name=\"prefixIcon\"\n                :customStyle=\"$u.addStyle(iconStyle)\"\n            ></u-icon>\n        </view>\n        <u-link\n            v-if=\"mode === 'link'\"\n            :text=\"value\"\n            :href=\"href\"\n            underLine\n        ></u-link>\n        <template v-else-if=\"openType && isMp\">\n            <button\n                class=\"u-reset-button u-text__value\"\n                :style=\"[valueStyle]\"\n                :data-index=\"index\"\n                :openType=\"openType\"\n                @getuserinfo=\"onGetUserInfo\"\n                @contact=\"onContact\"\n                @getphonenumber=\"onGetPhoneNumber\"\n                @error=\"onError\"\n                @launchapp=\"onLaunchApp\"\n                @opensetting=\"onOpenSetting\"\n                :lang=\"lang\"\n                :session-from=\"sessionFrom\"\n                :send-message-title=\"sendMessageTitle\"\n                :send-message-path=\"sendMessagePath\"\n                :send-message-img=\"sendMessageImg\"\n                :show-message-card=\"showMessageCard\"\n                :app-parameter=\"appParameter\"\n            >\n                {{ value }}\n            </button>\n        </template>\n        <text\n            v-else\n            class=\"u-text__value\"\n            :style=\"[valueStyle]\"\n            :class=\"[\n                type && `u-text__value--${type}`,\n                lines && `u-line-${lines}`\n            ]\"\n            >{{ value }}</text\n        >\n        <view class=\"u-text__suffix-icon\" v-if=\"suffixIcon\">\n            <u-icon\n                :name=\"suffixIcon\"\n                :customStyle=\"$u.addStyle(iconStyle)\"\n            ></u-icon>\n        </view>\n    </view>\n</template>\n\n<script>\nimport value from './value.js'\nimport button from '../../libs/mixin/button.js'\nimport openType from '../../libs/mixin/openType.js'\nimport props from './props.js'\n/**\n * Text 文本\n * @description 此组件集成了文本类在项目中的常用功能，包括状态，拨打电话，格式化日期，*替换，超链接...等功能。 您大可不必在使用特殊文本时自己定义，text组件几乎涵盖您能使用的大部分场景。\n * @tutorial https://www.uviewui.com/components/loading.html\n * @property {String} \t\t\t\t\ttype\t\t主题颜色\n * @property {Boolean} \t\t\t\t\tshow\t\t是否显示（默认 true ）\n * @property {String | Number}\t\t\ttext\t\t显示的值\n * @property {String}\t\t\t\t\tprefixIcon\t前置图标\n * @property {String} \t\t\t\t\tsuffixIcon\t后置图标\n * @property {String} \t\t\t\t\tmode\t\t文本处理的匹配模式 text-普通文本，price-价格，phone-手机号，name-姓名，date-日期，link-超链接\n * @property {String} \t\t\t\t\thref\t\tmode=link下，配置的链接\n * @property {String | Function} \t\tformat\t\t格式化规则\n * @property {Boolean} \t\t\t\t\tcall\t\tmode=phone时，点击文本是否拨打电话（默认 false ）\n * @property {String} \t\t\t\t\topenType\t小程序的打开方式\n * @property {Boolean} \t\t\t\t\tbold\t\t是否粗体，默认normal（默认 false ）\n * @property {Boolean} \t\t\t\t\tblock\t\t是否块状（默认 false ）\n * @property {String | Number} \t\t\tlines\t\t文本显示的行数，如果设置，超出此行数，将会显示省略号\n * @property {String} \t\t\t\t\tcolor\t\t文本颜色（默认 '#303133' ）\n * @property {String | Number} \t\t\tsize\t\t字体大小（默认 15 ）\n * @property {Object | String} \t\t\ticonStyle\t图标的样式 （默认 {fontSize: '15px'} ）\n * @property {String} \t\t\t\t\tdecoration\t文字装饰，下划线，中划线等，可选值 none|underline|line-through（默认 'none' ）\n * @property {Object | String | Number}\tmargin\t\t外边距，对象、字符串，数值形式均可（默认 0 ）\n * @property {String | Number} \t\t\tlineHeight\t文本行高\n * @property {String} \t\t\t\t\talign\t\t文本对齐方式，可选值left|center|right（默认 'left' ）\n * @property {String} \t\t\t\t\twordWrap\t文字换行，可选值break-word|normal|anywhere（默认 'normal' ）\n * @event {Function} click  点击触发事件\n * @example <u--text text=\"我用十年青春,赴你最后之约\"></u--text>\n */\nexport default {\n    name: 'u--text',\n    // #ifdef MP\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, value, button, openType, props],\n    // #endif\n    // #ifndef MP\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, value, props],\n    // #endif\n    computed: {\n        valueStyle() {\n            const style = {\n                textDecoration: this.decoration,\n                fontWeight: this.bold ? 'bold' : 'normal',\n                wordWrap: this.wordWrap,\n                fontSize: uni.$u.addUnit(this.size)\n            }\n            !this.type && (style.color = this.color)\n            this.isNvue && this.lines && (style.lines = this.lines)\n            this.lineHeight &&\n                (style.lineHeight = uni.$u.addUnit(this.lineHeight))\n            !this.isNvue && this.block && (style.display = 'block')\n            return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n        },\n        isNvue() {\n            let nvue = false\n            // #ifdef APP-NVUE\n            nvue = true\n            // #endif\n            return nvue\n        },\n        isMp() {\n            let mp = false\n            // #ifdef MP\n            mp = true\n            // #endif\n            return mp\n        }\n    },\n    data() {\n        return {}\n    },\n    methods: {\n        clickHandler() {\n            // 如果为手机号模式，拨打电话\n            if (this.call && this.mode === 'phone') {\n                uni.makePhoneCall({\n                    phoneNumber: this.text\n                })\n            }\n            this.$emit('click')\n        }\n    }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '../../libs/css/components.scss';\n\n.u-text {\n    @include flex(row);\n    align-items: center;\n    flex-wrap: nowrap;\n    flex: 1;\n\t/* #ifndef APP-NVUE */\n\twidth: 100%;\n\t/* #endif */\n\n    &__price {\n        font-size: 14px;\n        color: $u-content-color;\n    }\n\n    &__value {\n        font-size: 14px;\n        @include flex;\n        color: $u-content-color;\n        flex-wrap: wrap;\n        // flex: 1;\n        text-overflow: ellipsis;\n        align-items: center;\n\n        &--primary {\n            color: $u-primary;\n        }\n\n        &--warning {\n            color: $u-warning;\n        }\n\n        &--success {\n            color: $u-success;\n        }\n\n        &--info {\n            color: $u-info;\n        }\n\n        &--error {\n            color: $u-error;\n        }\n\n        &--main {\n            color: $u-main-color;\n        }\n\n        &--content {\n            color: $u-content-color;\n        }\n\n        &--tips {\n            color: $u-tips-color;\n        }\n\n        &--light {\n            color: $u-light-color;\n        }\n    }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-text/value.js",
    "content": "export default {\n    computed: {\n        // 经处理后需要显示的值\n        value() {\n            const {\n                text,\n                mode,\n                format,\n                href\n            } = this\n            // 价格类型\n            if (mode === 'price') {\n                // 如果text不为金额进行提示\n                if (!/^\\d+(\\.\\d+)?$/.test(text)) {\n                    uni.$u.error('金额模式下，text参数需要为金额格式');\n                }\n                // 进行格式化，判断用户传入的format参数为正则，或者函数，如果没有传入format，则使用默认的金额格式化处理\n                if (uni.$u.test.func(format)) {\n                    // 如果用户传入的是函数，使用函数格式化\n                    return format(text)\n                }\n                // 如果format非正则，非函数，则使用默认的金额格式化方法进行操作\n                return uni.$u.priceFormat(text, 2)\n            } if (mode === 'date') {\n                // 判断是否合法的日期或者时间戳\n                !uni.$u.test.date(text) && uni.$u.error('日期模式下，text参数需要为日期或时间戳格式')\n                // 进行格式化，判断用户传入的format参数为正则，或者函数，如果没有传入format，则使用默认的格式化处理\n                if (uni.$u.test.func(format)) {\n                    // 如果用户传入的是函数，使用函数格式化\n                    return format(text)\n                } if (format) {\n                    // 如果format非正则，非函数，则使用默认的时间格式化方法进行操作\n                    return uni.$u.timeFormat(text, format)\n                }\n                // 如果没有设置format，则设置为默认的时间格式化形式\n                return uni.$u.timeFormat(text, 'yyyy-mm-dd')\n            } if (mode === 'phone') {\n                // 判断是否合法的手机号\n                // !uni.$u.test.mobile(text) && uni.$u.error('手机号模式下，text参数需要为手机号码格式')\n                if (uni.$u.test.func(format)) {\n                    // 如果用户传入的是函数，使用函数格式化\n                    return format(text)\n                } if (format === 'encrypt') {\n                    // 如果format为encrypt，则将手机号进行星号加密处理\n                    return `${text.substr(0, 3)}****${text.substr(7)}`\n                }\n                return text\n            } if (mode === 'name') {\n                // 判断是否合法的字符粗\n                !(typeof (text) === 'string') && uni.$u.error('姓名模式下，text参数需要为字符串格式')\n                if (uni.$u.test.func(format)) {\n                    // 如果用户传入的是函数，使用函数格式化\n                    return format(text)\n                } if (format === 'encrypt') {\n                    // 如果format为encrypt，则将姓名进行星号加密处理\n                    return this.formatName(text)\n                }\n                return text\n            } if (mode === 'link') {\n                // 判断是否合法的字符粗\n                !uni.$u.test.url(href) && uni.$u.error('超链接模式下，href参数需要为URL格式')\n                return text\n            }\n            return text\n        }\n    },\n    methods: {\n        // 默认的姓名脱敏规则\n        formatName(name) {\n            let value = ''\n            if (name.length === 2) {\n                value = name.substr(0, 1) + '*'\n            } else if (name.length > 2) {\n                let char = ''\n                for (let i = 0, len = name.length - 2; i < len; i++) {\n                    char += '*'\n                }\n                value = name.substr(0, 1) + char + name.substr(-1, 1)\n            } else {\n                value = name\n            }\n            return value\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-textarea/props.js",
    "content": "export default {\n\tprops: {\n\t\t// 输入框的内容\n\t\tvalue: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.textarea.value\n\t\t},\n\t\t// 输入框为空时占位符\n\t\tplaceholder: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.textarea.placeholder\n\t\t},\n\t\t// 指定placeholder的样式类，注意页面或组件的style中写了scoped时，需要在类名前写/deep/\n\t\tplaceholderClass: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.input.placeholderClass\n\t\t},\n\t\t// 指定placeholder的样式\n\t\tplaceholderStyle: {\n\t\t\ttype: [String, Object],\n\t\t\tdefault: uni.$u.props.input.placeholderStyle\n\t\t},\n\t\t// 输入框高度\n\t\theight: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.textarea.height\n\t\t},\n\t\t// 设置键盘右下角按钮的文字，仅微信小程序，App-vue和H5有效\n\t\tconfirmType: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.textarea.confirmType\n\t\t},\n\t\t// 是否禁用\n\t\tdisabled: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.disabled\n\t\t},\n\t\t// 是否显示统计字数\n\t\tcount: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.count\n\t\t},\n\t\t// 是否自动获取焦点，nvue不支持，H5取决于浏览器的实现\n\t\tfocus: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.focus\n\t\t},\n\t\t// 是否自动增加高度\n\t\tautoHeight: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.autoHeight\n\t\t},\n\t\t// 如果textarea是在一个position:fixed的区域，需要显示指定属性fixed为true\n\t\tfixed: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.fixed\n\t\t},\n\t\t// 指定光标与键盘的距离\n\t\tcursorSpacing: {\n\t\t\ttype: Number,\n\t\t\tdefault: uni.$u.props.textarea.cursorSpacing\n\t\t},\n\t\t// 指定focus时的光标位置\n\t\tcursor: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.textarea.cursor\n\t\t},\n\t\t// 是否显示键盘上方带有”完成“按钮那一栏，\n\t\tshowConfirmBar: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.showConfirmBar\n\t\t},\n\t\t// 光标起始位置，自动聚焦时有效，需与selection-end搭配使用\n\t\tselectionStart: {\n\t\t\ttype: Number,\n\t\t\tdefault: uni.$u.props.textarea.selectionStart\n\t\t},\n\t\t// 光标结束位置，自动聚焦时有效，需与selection-start搭配使用\n\t\tselectionEnd: {\n\t\t\ttype: Number,\n\t\t\tdefault: uni.$u.props.textarea.selectionEnd\n\t\t},\n\t\t// 键盘弹起时，是否自动上推页面\n\t\tadjustPosition: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.adjustPosition\n\t\t},\n\t\t// 是否去掉 iOS 下的默认内边距，只微信小程序有效\n\t\tdisableDefaultPadding: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.disableDefaultPadding\n\t\t},\n\t\t// focus时，点击页面的时候不收起键盘，只微信小程序有效\n\t\tholdKeyboard: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: uni.$u.props.textarea.holdKeyboard\n\t\t},\n\t\t// 最大输入长度，设置为 -1 的时候不限制最大长度\n\t\tmaxlength: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: uni.$u.props.textarea.maxlength\n\t\t},\n\t\t// 边框类型，surround-四周边框，bottom-底部边框\n\t\tborder: {\n\t\t\ttype: String,\n\t\t\tdefault: uni.$u.props.textarea.border\n\t\t},\n\t\t// 用于处理或者过滤输入框内容的方法\n\t\tformatter: {\n\t\t\ttype: [Function, null],\n\t\t\tdefault: uni.$u.props.textarea.formatter\n\t\t},\n\t\t// 是否忽略组件内对文本合成系统事件的处理\n\t\tignoreCompositionEvent: {\n\t\t\ttype: Boolean,\n\t\t\tdefault: true\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-textarea/u-textarea.vue",
    "content": "<template>\n    <view class=\"u-textarea\" :class=\"textareaClass\" :style=\"[textareaStyle]\">\n        <textarea\n            class=\"u-textarea__field\"\n            :value=\"innerValue\"\n            :style=\"{ height: $u.addUnit(height) }\"\n            :placeholder=\"placeholder\"\n            :placeholder-style=\"$u.addStyle(placeholderStyle, 'string')\"\n            :placeholder-class=\"placeholderClass\"\n            :disabled=\"disabled\"\n            :focus=\"focus\"\n            :autoHeight=\"autoHeight\"\n            :fixed=\"fixed\"\n            :cursorSpacing=\"cursorSpacing\"\n            :cursor=\"cursor\"\n            :showConfirmBar=\"showConfirmBar\"\n            :selectionStart=\"selectionStart\"\n            :selectionEnd=\"selectionEnd\"\n            :adjustPosition=\"adjustPosition\"\n            :disableDefaultPadding=\"disableDefaultPadding\"\n            :holdKeyboard=\"holdKeyboard\"\n            :maxlength=\"maxlength\"\n            :confirmType=\"confirmType\"\n            :ignoreCompositionEvent=\"ignoreCompositionEvent\"\n            @focus=\"onFocus\"\n            @blur=\"onBlur\"\n            @linechange=\"onLinechange\"\n            @input=\"onInput\"\n            @confirm=\"onConfirm\"\n            @keyboardheightchange=\"onKeyboardheightchange\"\n        ></textarea>\n        <text\n            class=\"u-textarea__count\"\n            :style=\"{\n                'background-color': disabled ? 'transparent' : '#fff',\n            }\"\n            v-if=\"count\"\n            >{{ innerValue.length }}/{{ maxlength }}</text\n        >\n    </view>\n</template>\n\n<script>\nimport props from \"./props.js\";\n/**\n * Textarea 文本域\n * @description 文本域此组件满足了可能出现的表单信息补充，编辑等实际逻辑的功能，内置了字数校验等\n * @tutorial https://www.uviewui.com/components/textarea.html\n *\n * @property {String | Number} \t\tvalue\t\t\t\t\t输入框的内容\n * @property {String | Number}\t\tplaceholder\t\t\t\t输入框为空时占位符\n * @property {String}\t\t\t    placeholderClass\t\t指定placeholder的样式类，注意页面或组件的style中写了scoped时，需要在类名前写/deep/ （ 默认 'input-placeholder' ）\n * @property {String | Object}\t    placeholderStyle\t\t指定placeholder的样式，字符串/对象形式，如\"color: red;\"\n * @property {String | Number}\t\theight\t\t\t\t\t输入框高度（默认 70 ）\n * @property {String}\t\t\t\tconfirmType\t\t\t\t设置键盘右下角按钮的文字，仅微信小程序，App-vue和H5有效（默认 'done' ）\n * @property {Boolean}\t\t\t\tdisabled\t\t\t\t是否禁用（默认 false ）\n * @property {Boolean}\t\t\t\tcount\t\t\t\t\t是否显示统计字数（默认 false ）\n * @property {Boolean}\t\t\t\tfocus\t\t\t\t\t是否自动获取焦点，nvue不支持，H5取决于浏览器的实现（默认 false ）\n * @property {Boolean | Function}\tautoHeight\t\t\t\t是否自动增加高度（默认 false ）\n * @property {Boolean}\t\t\t\tfixed\t\t\t\t\t如果textarea是在一个position:fixed的区域，需要显示指定属性fixed为true（默认 false ）\n * @property {Number}\t\t\t\tcursorSpacing\t\t\t指定光标与键盘的距离（默认 0 ）\n * @property {String | Number}\t\tcursor\t\t\t\t\t指定focus时的光标位置\n * @property {Function}\t\t\t    formatter\t\t\t    内容式化函数\n * @property {Boolean}\t\t\t\tshowConfirmBar\t\t\t是否显示键盘上方带有”完成“按钮那一栏，（默认 true ）\n * @property {Number}\t\t\t\tselectionStart\t\t\t光标起始位置，自动聚焦时有效，需与selection-end搭配使用，（默认 -1 ）\n * @property {Number | Number}\t\tselectionEnd\t\t\t光标结束位置，自动聚焦时有效，需与selection-start搭配使用（默认 -1 ）\n * @property {Boolean}\t\t\t\tadjustPosition\t\t\t键盘弹起时，是否自动上推页面（默认 true ）\n * @property {Boolean | Number}\t\tdisableDefaultPadding\t是否去掉 iOS 下的默认内边距，只微信小程序有效（默认 false ）\n * @property {Boolean}\t\t\t\tholdKeyboard\t\t\tfocus时，点击页面的时候不收起键盘，只微信小程序有效（默认 false ）\n * @property {String | Number}\t\tmaxlength\t\t\t\t最大输入长度，设置为 -1 的时候不限制最大长度（默认 140 ）\n * @property {String}\t\t\t\tborder\t\t\t\t\t边框类型，surround-四周边框，none-无边框，bottom-底部边框（默认 'surround' ）\n * @property {Boolean}\t\t\t\tignoreCompositionEvent\t是否忽略组件内对文本合成系统事件的处理\n *\n * @event {Function(e)} focus\t\t\t\t\t输入框聚焦时触发，event.detail = { value, height }，height 为键盘高度\n * @event {Function(e)} blur\t\t\t\t\t输入框失去焦点时触发，event.detail = {value, cursor}\n * @event {Function(e)} linechange\t\t\t\t输入框行数变化时调用，event.detail = {height: 0, heightRpx: 0, lineCount: 0}\n * @event {Function(e)} input\t\t\t\t\t当键盘输入时，触发 input 事件\n * @event {Function(e)} confirm\t\t\t\t\t点击完成时， 触发 confirm 事件\n * @event {Function(e)} keyboardheightchange\t键盘高度发生变化的时候触发此事件\n * @example <u--textarea v-model=\"value1\" placeholder=\"请输入内容\" ></u--textarea>\n */\nexport default {\n    name: \"u-textarea\",\n    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\tdata() {\n\t\treturn {\n\t\t\t// 输入框的值\n\t\t\tinnerValue: \"\",\n\t\t\t// 是否处于获得焦点状态\n\t\t\tfocused: false,\n\t\t\t// value是否第一次变化，在watch中，由于加入immediate属性，会在第一次触发，此时不应该认为value发生了变化\n\t\t\tfirstChange: true,\n\t\t\t// value绑定值的变化是由内部还是外部引起的\n\t\t\tchangeFromInner: false,\n\t\t\t// 过滤处理方法\n\t\t\tinnerFormatter: value => value\n\t\t}\n\t},\n\twatch: {\n\t    value: {\n\t        immediate: true,\n\t        handler(newVal, oldVal) {\n\t            this.innerValue = newVal;\n\t            /* #ifdef H5 */\n\t            // 在H5中，外部value变化后，修改input中的值，不会触发@input事件，此时手动调用值变化方法\n\t            if (\n\t                this.firstChange === false &&\n\t                this.changeFromInner === false\n\t            ) {\n\t                this.valueChange();\n\t            }\n\t            /* #endif */\n\t            this.firstChange = false;\n\t            // 重置changeFromInner的值为false，标识下一次引起默认为外部引起的\n\t            this.changeFromInner = false;\n\t        },\n\t    },\n\t},\n    computed: {\n        // 组件的类名\n        textareaClass() {\n            let classes = [],\n                { border, disabled, shape } = this;\n            border === \"surround\" &&\n                (classes = classes.concat([\"u-border\", \"u-textarea--radius\"]));\n            border === \"bottom\" &&\n                (classes = classes.concat([\n                    \"u-border-bottom\",\n                    \"u-textarea--no-radius\",\n                ]));\n            disabled && classes.push(\"u-textarea--disabled\");\n            return classes.join(\" \");\n        },\n        // 组件的样式\n        textareaStyle() {\n            const style = {};\n            // #ifdef APP-NVUE\n            // 由于textarea在安卓nvue上的差异性，需要额外再调整其内边距\n            if (uni.$u.os() === \"android\") {\n                style.paddingTop = \"6px\";\n                style.paddingLeft = \"9px\";\n                style.paddingBottom = \"3px\";\n                style.paddingRight = \"6px\";\n            }\n            // #endif\n            return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));\n        },\n    },\n    methods: {\n\t\t// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用\n\t\tsetFormatter(e) {\n\t\t\tthis.innerFormatter = e\n\t\t},\n        onFocus(e) {\n            this.$emit(\"focus\", e);\n        },\n        onBlur(e) {\n            this.$emit(\"blur\", e);\n            // 尝试调用u-form的验证方法\n            uni.$u.formValidate(this, \"blur\");\n        },\n        onLinechange(e) {\n            this.$emit(\"linechange\", e);\n        },\n        onInput(e) {\n\t\t\tlet { value = \"\" } = e.detail || {};\n\t\t\t// 格式化过滤方法\n\t\t\tconst formatter = this.formatter || this.innerFormatter\n\t\t\tconst formatValue = formatter(value)\n\t\t\t// 为了避免props的单向数据流特性，需要先将innerValue值设置为当前值，再在$nextTick中重新赋予设置后的值才有效\n\t\t\tthis.innerValue = value\n\t\t\tthis.$nextTick(() => {\n\t\t\t\tthis.innerValue = formatValue;\n\t\t\t\tthis.valueChange();\n\t\t\t})\n        },\n\t\t// 内容发生变化，进行处理\n\t\tvalueChange() {\n\t\t    const value = this.innerValue;\n\t\t    this.$nextTick(() => {\n\t\t        this.$emit(\"input\", value);\n\t\t        // 标识value值的变化是由内部引起的\n\t\t        this.changeFromInner = true;\n\t\t        this.$emit(\"change\", value);\n\t\t        // 尝试调用u-form的验证方法\n\t\t        uni.$u.formValidate(this, \"change\");\n\t\t    });\n\t\t},\n        onConfirm(e) {\n            this.$emit(\"confirm\", e);\n        },\n        onKeyboardheightchange(e) {\n            this.$emit(\"keyboardheightchange\", e);\n        },\n    },\n};\n</script>\n\n<style lang=\"scss\" scoped>\n@import \"../../libs/css/components.scss\";\n\n.u-textarea {\n    border-radius: 4px;\n    background-color: #fff;\n    position: relative;\n    @include flex;\n    flex: 1;\n\tpadding: 9px;\n\n    &--radius {\n        border-radius: 4px;\n    }\n\n    &--no-radius {\n        border-radius: 0;\n    }\n\n    &--disabled {\n        background-color: #f5f7fa;\n    }\n\n    &__field {\n        flex: 1;\n        font-size: 15px;\n        color: $u-content-color;\n\t\twidth: 100%;\n    }\n\n    &__count {\n        position: absolute;\n        right: 5px;\n        bottom: 2px;\n        font-size: 12px;\n        color: $u-tips-color;\n        background-color: #ffffff;\n        padding: 1px 4px;\n    }\n}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-toast/u-toast.vue",
    "content": "<template>\n\t<view class=\"u-toast\">\n\t\t<u-overlay\n\t\t\t:show=\"isShow\"\n\t\t\t:custom-style=\"overlayStyle\"\n\t\t>\n\t\t\t<view\n\t\t\t\tclass=\"u-toast__content\"\n\t\t\t\t:style=\"[contentStyle]\"\n\t\t\t\t:class=\"['u-type-' + tmpConfig.type, (tmpConfig.type === 'loading' || tmpConfig.loading) ?  'u-toast__content--loading' : '']\"\n\t\t\t>\n\t\t\t\t<u-loading-icon\n\t\t\t\t\tv-if=\"tmpConfig.type === 'loading'\"\n\t\t\t\t\tmode=\"circle\"\n\t\t\t\t\tcolor=\"rgb(255, 255, 255)\"\n\t\t\t\t\tinactiveColor=\"rgb(120, 120, 120)\"\n\t\t\t\t\tsize=\"25\"\n\t\t\t\t></u-loading-icon>\n\t\t\t\t<u-icon\n\t\t\t\t\tv-else-if=\"tmpConfig.type !== 'defalut' && iconName\"\n\t\t\t\t\t:name=\"iconName\"\n\t\t\t\t\tsize=\"17\"\n\t\t\t\t\t:color=\"tmpConfig.type\"\n\t\t\t\t\t:customStyle=\"iconStyle\"\n\t\t\t\t></u-icon>\n\t\t\t\t<u-gap\n\t\t\t\t\tv-if=\"tmpConfig.type === 'loading' || tmpConfig.loading\"\n\t\t\t\t\theight=\"12\"\n\t\t\t\t\tbgColor=\"transparent\"\n\t\t\t\t></u-gap>\n\t\t\t\t<text\n\t\t\t\t\tclass=\"u-toast__content__text\"\n\t\t\t\t\t:class=\"['u-toast__content__text--' + tmpConfig.type]\"\n\t\t\t\t\tstyle=\"max-width: 400rpx;\"\n\t\t\t\t>{{ tmpConfig.message }}</text>\n\t\t\t</view>\n\t\t</u-overlay>\n\t</view>\n</template>\n\n<script>\n\t/**\n\t * toast 消息提示\n\t * @description 此组件表现形式类似uni的uni.showToastAPI，但也有不同的地方。\n\t * @tutorial https://www.uviewui.com/components/toast.html\n\t * @property {String | Number}\tzIndex\t\ttoast展示时的zIndex值 (默认 10090 )\n\t * @property {Boolean}\t\t\tloading\t\t是否加载中 （默认 false ）\n\t * @property {String | Number}\tmessage\t\t显示的文字内容\n\t * @property {String}\t\t\ticon\t\t图标，或者绝对路径的图片\n\t * @property {String}\t\t\ttype\t\t主题类型 （默认 default）\n\t * @property {Boolean}\t\t\tshow\t\t是否显示该组件 （默认 false）\n\t * @property {Boolean}\t\t\toverlay\t\t是否显示透明遮罩，防止点击穿透 （默认 false ）\n\t * @property {String}\t\t\tposition\t位置 （默认 'center' ）\n\t * @property {Object}\t\t\tparams\t\t跳转的参数 \n\t * @property {String | Number}  duration\t展示时间，单位ms （默认 2000 ）\n\t * @property {Boolean}\t\t\tisTab\t\t是否返回的为tab页面 （默认 false ）\n\t * @property {String}\t\t\turl\t\t\ttoast消失后是否跳转页面，有则跳转，优先级高于back参数 \n\t * @property {Function}\t\t\tcomplete\t执行完后的回调函数 \n\t * @property {Boolean}\t\t\tback\t\t结束toast是否自动返回上一页 （默认 false ）\n\t * @property {Object}\t\t\tcustomStyle\t组件的样式，对象形式\n\t * @event {Function} show 显示toast，如需一进入页面就显示toast，请在onReady生命周期调用\n\t * @example <u-toast ref=\"uToast\" />\n\t */\n\texport default {\n\t\tname: 'u-toast',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\tisShow: false,\n\t\t\t\ttimer: null, // 定时器\n\t\t\t\tconfig: {\n\t\t\t\t\tmessage: '', // 显示文本\n\t\t\t\t\ttype: '', // 主题类型，primary，success，error，warning，black\n\t\t\t\t\tduration: 2000, // 显示的时间，毫秒\n\t\t\t\t\ticon: true, // 显示的图标\n\t\t\t\t\tposition: 'center', // toast出现的位置\n\t\t\t\t\tcomplete: null, // 执行完后的回调函数\n\t\t\t\t\toverlay: false, // 是否防止触摸穿透\n\t\t\t\t\tloading: false, // 是否加载中状态\n\t\t\t\t},\n\t\t\t\ttmpConfig: {}, // 将用户配置和内置配置合并后的临时配置变量\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\ticonName() {\n\t\t\t\t// 只有不为none，并且type为error|warning|succes|info时候，才显示图标\n\t\t\t\tif(!this.tmpConfig.icon || this.tmpConfig.icon == 'none') {\n\t\t\t\t\treturn '';\n\t\t\t\t}\n\t\t\t\tif (['error', 'warning', 'success', 'primary'].includes(this.tmpConfig.type)) {\n\t\t\t\t\treturn uni.$u.type2icon(this.tmpConfig.type)\n\t\t\t\t} else {\n\t\t\t\t\treturn ''\n\t\t\t\t}\n\t\t\t},\n\t\t\toverlayStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\tjustifyContent: 'center',\n\t\t\t\t\talignItems: 'center',\n\t\t\t\t\tdisplay: 'flex'\n\t\t\t\t}\n\t\t\t\t// 将遮罩设置为100%透明度，避免出现灰色背景\n\t\t\t\tstyle.backgroundColor = 'rgba(0, 0, 0, 0)'\n\t\t\t\treturn style\n\t\t\t},\n\t\t\ticonStyle() {\n\t\t\t\tconst style = {}\n\t\t\t\t// 图标需要一个右边距，以跟右边的文字有隔开的距离\n\t\t\t\tstyle.marginRight = '4px'\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// iOSAPP下，图标有1px的向下偏移，这里进行修正\n\t\t\t\tif (uni.$u.os() === 'ios') {\n\t\t\t\t\tstyle.marginTop = '-1px'\n\t\t\t\t}\n\t\t\t\t// #endif\n\t\t\t\treturn style\n\t\t\t},\n\t\t\tloadingIconColor() {\n\t\t\t\tlet color = 'rgb(255, 255, 255)'\n\t\t\t\tif (['error', 'warning', 'success', 'primary'].includes(this.tmpConfig.type)) {\n\t\t\t\t\t// loading-icon组件内部会对color参数进行一个透明度处理，该方法要求传入的颜色值\n\t\t\t\t\t// 必须为rgb格式的，所以这里做一个处理\n\t\t\t\t\tcolor = uni.$u.hexToRgb(uni.$u.color[this.tmpConfig.type])\n\t\t\t\t}\n\t\t\t\treturn color\n\t\t\t},\n\t\t\t// 内容盒子的样式\n\t\t\tcontentStyle() {\n\t\t\t\tconst windowHeight = uni.$u.sys().windowHeight, style = {}\n\t\t\t\tlet value = 0\n\t\t\t\t// 根据top和bottom，对Y轴进行窗体高度的百分比偏移\n\t\t\t\tif(this.tmpConfig.position === 'top') {\n\t\t\t\t\tvalue = - windowHeight * 0.25\n\t\t\t\t} else if(this.tmpConfig.position === 'bottom') {\n\t\t\t\t\tvalue = windowHeight * 0.25\n\t\t\t\t}\n\t\t\t\tstyle.transform = `translateY(${value}px)`\n\t\t\t\treturn style\n\t\t\t}\n\t\t},\n\t\tcreated() {\n\t\t\t// 通过主题的形式调用toast，批量生成方法函数\n\t\t\t['primary', 'success', 'error', 'warning', 'default', 'loading'].map(item => {\n\t\t\t\tthis[item] = message => this.show({\n\t\t\t\t\ttype: item,\n\t\t\t\t\tmessage\n\t\t\t\t})\n\t\t\t})\n\t\t},\n\t\tmethods: {\n\t\t\t// 显示toast组件，由父组件通过this.$refs.xxx.show(options)形式调用\n\t\t\tshow(options) {\n\t\t\t\t// 不将结果合并到this.config变量，避免多次调用u-toast，前后的配置造成混乱\n\t\t\t\tthis.tmpConfig = uni.$u.deepMerge(this.config, options)\n\t\t\t\t// 清除定时器\n\t\t\t\tthis.clearTimer()\n\t\t\t\tthis.isShow = true\n\t\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\t\t// 倒计时结束，清除定时器，隐藏toast组件\n\t\t\t\t\tthis.clearTimer()\n\t\t\t\t\t// 判断是否存在callback方法，如果存在就执行\n\t\t\t\t\ttypeof(this.tmpConfig.complete) === 'function' && this.tmpConfig.complete()\n\t\t\t\t}, this.tmpConfig.duration)\n\t\t\t},\n\t\t\t// 隐藏toast组件，由父组件通过this.$refs.xxx.hide()形式调用\n\t\t\thide() {\n\t\t\t\tthis.clearTimer()\n\t\t\t},\n\t\t\tclearTimer() {\n\t\t\t\tthis.isShow = false\n\t\t\t\t// 清除定时器\n\t\t\t\tclearTimeout(this.timer)\n\t\t\t\tthis.timer = null\n\t\t\t}\n\t\t},\n\t\tbeforeDestroy() {\n\t\t\tthis.clearTimer()\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t$u-toast-color:#fff !default;\n\t$u-toast-border-radius:4px !default;\n\t$u-toast-border-background-color:#585858 !default;\n\t$u-toast-border-font-size:14px !default;\n\t$u-toast-border-padding:12px 20px !default;\n\t$u-toast-loading-border-padding: 20px 20px !default;\n\t$u-toast-content-text-color:#fff !default;\n\t$u-toast-content-text-font-size:15px !default;\n\t$u-toast-u-icon:10rpx !default;\n\t$u-toast-u-type-primary-color:$u-primary !default;\n\t$u-toast-u-type-primary-background-color:#ecf5ff !default;\n\t$u-toast-u-type-primary-border-color:rgb(215, 234, 254) !default;\n\t$u-toast-u-type-primary-border-width:1px !default;\n\t$u-toast-u-type-success-color: $u-success !default;\n\t$u-toast-u-type-success-background-color: #dbf1e1 !default;\n\t$u-toast-u-type-success-border-color: #BEF5C8 !default;\n\t$u-toast-u-type-success-border-width: 1px !default;\n\t$u-toast-u-type-error-color:$u-error !default;\n\t$u-toast-u-type-error-background-color:#fef0f0 !default;\n\t$u-toast-u-type-error-border-color:#fde2e2 !default;\n\t$u-toast-u-type-error-border-width: 1px !default;\n\t$u-toast-u-type-warning-color:$u-warning !default;\n\t$u-toast-u-type-warning-background-color:#fdf6ec !default;\n\t$u-toast-u-type-warning-border-color:#faecd8 !default;\n\t$u-toast-u-type-warning-border-width: 1px !default;\n\t$u-toast-u-type-default-color:#fff !default;\n\t$u-toast-u-type-default-background-color:#585858 !default;\n\n\t.u-toast {\n\t\t&__content {\n\t\t\t@include flex;\n\t\t\tpadding: $u-toast-border-padding;\n\t\t\tborder-radius: $u-toast-border-radius;\n\t\t\tbackground-color: $u-toast-border-background-color;\n\t\t\tcolor: $u-toast-color;\n\t\t\talign-items: center;\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tmax-width: 600rpx;\n\t\t\t/* #endif */\n\t\t\tposition: relative;\n\n\t\t\t&--loading {\n\t\t\t\tflex-direction: column;\n\t\t\t\tpadding: $u-toast-loading-border-padding;\n\t\t\t}\n\n\t\t\t&__text {\n\t\t\t\tcolor: $u-toast-content-text-color;\n\t\t\t\tfont-size: $u-toast-content-text-font-size;\n\t\t\t\tline-height: $u-toast-content-text-font-size;\n\n\t\t\t\t&--default {\n\t\t\t\t\tcolor: $u-toast-content-text-color;\n\t\t\t\t}\n\n\t\t\t\t&--error {\n\t\t\t\t\tcolor: $u-error;\n\t\t\t\t}\n\n\t\t\t\t&--primary {\n\t\t\t\t\tcolor: $u-primary;\n\t\t\t\t}\n\n\t\t\t\t&--success {\n\t\t\t\t\tcolor: $u-success;\n\t\t\t\t}\n\n\t\t\t\t&--warning {\n\t\t\t\t\tcolor: $u-warning;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t.u-type-primary {\n\t\tcolor: $u-toast-u-type-primary-color;\n\t\tbackground-color: $u-toast-u-type-primary-background-color;\n\t\tborder-color: $u-toast-u-type-primary-border-color;\n\t\tborder-width: $u-toast-u-type-primary-border-width;\n\t}\n\n\t.u-type-success {\n\t\tcolor: $u-toast-u-type-success-color;\n\t\tbackground-color: $u-toast-u-type-success-background-color;\n\t\tborder-color: $u-toast-u-type-success-border-color;\n\t\tborder-width: 1px;\n\t}\n\n\t.u-type-error {\n\t\tcolor: $u-toast-u-type-error-color;\n\t\tbackground-color: $u-toast-u-type-error-background-color;\n\t\tborder-color: $u-toast-u-type-error-border-color;\n\t\tborder-width: $u-toast-u-type-error-border-width;\n\t}\n\n\t.u-type-warning {\n\t\tcolor: $u-toast-u-type-warning-color;\n\t\tbackground-color: $u-toast-u-type-warning-background-color;\n\t\tborder-color: $u-toast-u-type-warning-border-color;\n\t\tborder-width: 1px;\n\t}\n\n\t.u-type-default {\n\t\tcolor: $u-toast-u-type-default-color;\n\t\tbackground-color: $u-toast-u-type-default-background-color;\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-toolbar/props.js",
    "content": "export default {\n    props: {\n        // 是否展示工具条\n        show: {\n            type: Boolean,\n            default: uni.$u.props.toolbar.show\n        },\n        // 取消按钮的文字\n        cancelText: {\n            type: String,\n            default: uni.$u.props.toolbar.cancelText\n        },\n        // 确认按钮的文字\n        confirmText: {\n            type: String,\n            default: uni.$u.props.toolbar.confirmText\n        },\n        // 取消按钮的颜色\n        cancelColor: {\n            type: String,\n            default: uni.$u.props.toolbar.cancelColor\n        },\n        // 确认按钮的颜色\n        confirmColor: {\n            type: String,\n            default: uni.$u.props.toolbar.confirmColor\n        },\n        // 标题文字\n        title: {\n            type: String,\n            default: uni.$u.props.toolbar.title\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-toolbar\"\n\t\t@touchmove.stop.prevent=\"noop\"\n\t\tv-if=\"show\"\n\t>\n\t\t<view\n\t\t\tclass=\"u-toolbar__cancel__wrapper\"\n\t\t\thover-class=\"u-hover-class\"\n\t\t>\n\t\t\t<text\n\t\t\t\tclass=\"u-toolbar__wrapper__cancel\"\n\t\t\t\t@tap=\"cancel\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tcolor: cancelColor\n\t\t\t\t}\"\n\t\t\t>{{ cancelText }}</text>\n\t\t</view>\n\t\t<text\n\t\t\tclass=\"u-toolbar__title u-line-1\"\n\t\t\tv-if=\"title\"\n\t\t>{{ title }}</text>\n\t\t<view\n\t\t\tclass=\"u-toolbar__confirm__wrapper\"\n\t\t\thover-class=\"u-hover-class\"\n\t\t>\n\t\t\t<text\n\t\t\t\tclass=\"u-toolbar__wrapper__confirm\"\n\t\t\t\t@tap=\"confirm\"\n\t\t\t\t:style=\"{\n\t\t\t\tcolor: confirmColor\n\t\t\t}\"\n\t\t\t>{{ confirmText }}</text>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Toolbar 工具条\n\t * @description \n\t * @tutorial https://www.uviewui.com/components/toolbar.html\n\t * @property {Boolean}\tshow\t\t\t是否展示工具条（默认 true ）\n\t * @property {String}\tcancelText\t\t取消按钮的文字（默认 '取消' ）\n\t * @property {String}\tconfirmText\t\t确认按钮的文字（默认 '确认' ）\n\t * @property {String}\tcancelColor\t\t取消按钮的颜色（默认 '#909193' ）\n\t * @property {String}\tconfirmColor\t确认按钮的颜色（默认 '#3c9cff' ）\n\t * @property {String}\ttitle\t\t\t标题文字\n\t * @event {Function} \n\t * @example \n\t */\n\texport default {\n\t\tname: 'u-toolbar',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\n\t\tmethods: {\n\t\t\t// 点击取消按钮\n\t\t\tcancel() {\n\t\t\t\tthis.$emit('cancel')\n\t\t\t},\n\t\t\t// 点击确定按钮\n\t\t\tconfirm() {\n\t\t\t\tthis.$emit('confirm')\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-toolbar {\n\t\theight: 42px;\n\t\t@include flex;\n\t\tjustify-content: space-between;\n\t\talign-items: center;\n\n\t\t&__wrapper {\n\t\t\t&__cancel {\n\t\t\t\tcolor: $u-tips-color;\n\t\t\t\tfont-size: 15px;\n\t\t\t\tpadding: 0 15px;\n\t\t\t}\n\t\t}\n\n\t\t&__title {\n\t\t\tcolor: $u-main-color;\n\t\t\tpadding: 0 60rpx;\n\t\t\tfont-size: 16px;\n\t\t\tflex: 1;\n\t\t\ttext-align: center;\n\t\t}\n\n\t\t&__wrapper {\n\t\t\t&__confirm {\n\t\t\t\tcolor: $u-primary;\n\t\t\t\tfont-size: 15px;\n\t\t\t\tpadding: 0 15px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tooltip/props.js",
    "content": "export default {\n    props: {\n        // 需要显示的提示文字\n        text: {\n            type: [String, Number],\n            default: uni.$u.props.tooltip.text\n        },\n        // 点击复制按钮时，复制的文本，为空则使用text值\n        copyText: {\n            type: [String, Number],\n            default: uni.$u.props.tooltip.copyText\n        },\n        // 文本大小\n        size: {\n            type: [String, Number],\n            default: uni.$u.props.tooltip.size\n        },\n        // 字体颜色\n        color: {\n            type: String,\n            default: uni.$u.props.tooltip.color\n        },\n        // 弹出提示框时，文本的背景色\n        bgColor: {\n            type: String,\n            default: uni.$u.props.tooltip.bgColor\n        },\n        // 弹出提示的方向，top-上方，bottom-下方\n        direction: {\n            type: String,\n            default: uni.$u.props.tooltip.direction\n        },\n        // 弹出提示的z-index，nvue无效\n        zIndex: {\n            type: [String, Number],\n            default: uni.$u.props.tooltip.zIndex\n        },\n        // 是否显示复制按钮\n        showCopy: {\n            type: Boolean,\n            default: uni.$u.props.tooltip.showCopy\n        },\n        // 扩展的按钮组\n        buttons: {\n            type: Array,\n            default: uni.$u.props.tooltip.buttons\n        },\n        // 是否显示透明遮罩以防止触摸穿透\n        overlay: {\n            type: Boolean,\n            default: uni.$u.props.tooltip.overlay\n        },\n        // 是否显示复制成功或者失败的toast\n        showToast: {\n            type: Boolean,\n            default: uni.$u.props.tooltip.showToast\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tooltip/u-tooltip.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"u-tooltip\"\n\t\t:style=\"[$u.addStyle(customStyle)]\"\n\t>\n\t\t<u-overlay\n\t\t\t:show=\"showTooltip && tooltipTop !== -10000 && overlay\"\n\t\t\tcustomStyle=\"backgroundColor: rgba(0, 0, 0, 0)\"\n\t\t\t@click=\"overlayClickHandler\"\n\t\t></u-overlay>\n\t\t<view class=\"u-tooltip__wrapper\">\n\t\t\t<text\n\t\t\t\tclass=\"u-tooltip__wrapper__text\"\n\t\t\t\t:id=\"textId\"\n\t\t\t\t:ref=\"textId\"\n\t\t\t\t:userSelect=\"false\"\n\t\t\t\t:selectable=\"false\"\n\t\t\t\t@longpress.stop=\"longpressHandler\"\n\t\t\t\t:style=\"{\n\t\t\t\t\tcolor: color,\n\t\t\t\t\tbackgroundColor: bgColor && showTooltip && tooltipTop !== -10000 ? bgColor : 'transparent'\n\t\t\t\t}\"\n\t\t\t>{{ text }}</text>\n\t\t\t<u-transition\n\t\t\t\tmode=\"fade\"\n\t\t\t\t:show=\"showTooltip\"\n\t\t\t\tduration=\"300\"\n\t\t\t\t:customStyle=\"{\n\t\t\t\t\tposition: 'absolute', \n\t\t\t\t\ttop: $u.addUnit(tooltipTop),\n\t\t\t\t\tzIndex: zIndex,\n\t\t\t\t\t...tooltipStyle\n\t\t\t\t}\"\n\t\t\t>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"u-tooltip__wrapper__popup\"\n\t\t\t\t\t:id=\"tooltipId\"\n\t\t\t\t\t:ref=\"tooltipId\"\n\t\t\t\t>\n\t\t\t\t\t<view\n\t\t\t\t\t\tclass=\"u-tooltip__wrapper__popup__indicator\"\n\t\t\t\t\t\thover-class=\"u-tooltip__wrapper__popup__indicator--hover\"\n\t\t\t\t\t\tv-if=\"showCopy || buttons.length\"\n\t\t\t\t\t\t:style=\"[indicatorStyle, {\n\t\t\t\t\t\t\twidth: $u.addUnit(indicatorWidth),\n\t\t\t\t\t\t\theight: $u.addUnit(indicatorWidth),\n\t\t\t\t\t\t}]\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<!-- 由于nvue不支持三角形绘制，这里就做一个四方形，再旋转45deg，得到露出的一个三角 -->\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"u-tooltip__wrapper__popup__list\">\n\t\t\t\t\t\t<view\n\t\t\t\t\t\t\tv-if=\"showCopy\"\n\t\t\t\t\t\t\tclass=\"u-tooltip__wrapper__popup__list__btn\"\n\t\t\t\t\t\t\thover-class=\"u-tooltip__wrapper__popup__list__btn--hover\"\n\t\t\t\t\t\t\t@tap=\"setClipboardData\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\tclass=\"u-tooltip__wrapper__popup__list__btn__text\"\n\t\t\t\t\t\t\t>复制</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<u-line\n\t\t\t\t\t\t\tdirection=\"column\"\n\t\t\t\t\t\t\tcolor=\"#8d8e90\"\n\t\t\t\t\t\t\tv-if=\"showCopy && buttons.length > 0\"\n\t\t\t\t\t\t\tlength=\"18\"\n\t\t\t\t\t\t></u-line>\n\t\t\t\t\t\t<block v-for=\"(item , index) in buttons\" :key=\"index\">\n\t\t\t\t\t\t\t<view\n\t\t\t\t\t\t\t\tclass=\"u-tooltip__wrapper__popup__list__btn\"\n\t\t\t\t\t\t\t\thover-class=\"u-tooltip__wrapper__popup__list__btn--hover\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<text\n\t\t\t\t\t\t\t\t\tclass=\"u-tooltip__wrapper__popup__list__btn__text\"\n\t\t\t\t\t\t\t\t\t@tap=\"btnClickHandler(index)\"\n\t\t\t\t\t\t\t\t>{{ item }}</text>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<u-line\n\t\t\t\t\t\t\t\tdirection=\"column\"\n\t\t\t\t\t\t\t\tcolor=\"#8d8e90\"\n\t\t\t\t\t\t\t\tv-if=\"index < buttons.length - 1\"\n\t\t\t\t\t\t\t\tlength=\"18\"\n\t\t\t\t\t\t\t></u-line>\n\t\t\t\t\t\t</block>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</u-transition>\n\t\t</view>\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t// #ifdef APP-NVUE \n\tconst dom = uni.requireNativePlugin('dom')\n\t// #endif\n\t// #ifdef H5\n\timport ClipboardJS from \"./clipboard.min.js\"\n\t// #endif\n\t/**\n\t * Tooltip \n\t * @description \n\t * @tutorial https://www.uviewui.com/components/tooltip.html\n\t * @property {String | Number}\ttext\t\t需要显示的提示文字\n\t * @property {String | Number}\tcopyText\t点击复制按钮时，复制的文本，为空则使用text值\n\t * @property {String | Number}\tsize\t\t文本大小（默认 14 ）\n\t * @property {String}\t\t\tcolor\t\t字体颜色（默认 '#606266' ）\n\t * @property {String}\t\t\tbgColor\t\t弹出提示框时，文本的背景色（默认 'transparent' ）\n\t * @property {String}\t\t\tdirection\t弹出提示的方向，top-上方，bottom-下方（默认 'top' ）\n\t * @property {String | Number}\tzIndex\t\t弹出提示的z-index，nvue无效（默认 10071 ）\n\t * @property {Boolean}\t\t\tshowCopy\t是否显示复制按钮（默认 true ）\n\t * @property {Array}\t\t\tbuttons\t\t扩展的按钮组\n\t * @property {Boolean}\t\t\toverlay\t\t是否显示透明遮罩以防止触摸穿透（默认 true ）\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\n\t * \n\t * @event {Function} \n\t * @example \n\t */\n\texport default {\n\t\tname: 'u-tooltip',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// 是否展示气泡\n\t\t\t\tshowTooltip: true,\n\t\t\t\t// 生成唯一id，防止一个页面多个组件，造成干扰\n\t\t\t\ttextId: uni.$u.guid(),\n\t\t\t\ttooltipId: uni.$u.guid(),\n\t\t\t\t// 初始时甚至为很大的值，让其移到屏幕外面，为了计算元素的尺寸\n\t\t\t\ttooltipTop: -10000,\n\t\t\t\t// 气泡的位置信息\n\t\t\t\ttooltipInfo: {\n\t\t\t\t\twidth: 0,\n\t\t\t\t\tleft: 0\n\t\t\t\t},\n\t\t\t\t// 文本的位置信息\n\t\t\t\ttextInfo: {\n\t\t\t\t\twidth: 0,\n\t\t\t\t\tleft: 0\n\t\t\t\t},\n\t\t\t\t// 三角形指示器的样式\n\t\t\t\tindicatorStyle: {},\n\t\t\t\t// 气泡在可能超出屏幕边沿范围时，重新定位后，距离屏幕边沿的距离\n\t\t\t\tscreenGap: 12,\n\t\t\t\t// 三角形指示器的宽高，由于对元素进行了角度旋转，精确计算指示器位置时，需要用到其尺寸信息\n\t\t\t\tindicatorWidth: 14,\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\tpropsChange() {\n\t\t\t\tthis.getElRect()\n\t\t\t}\n\t\t},\n\t\tcomputed: {\n\t\t\t// 特别处理H5的复制，因为H5浏览器是自带系统复制功能的，在H5环境\n\t\t\t// 当一些依赖参数变化时，需要重新计算气泡和指示器的位置信息\n\t\t\tpropsChange() {\n\t\t\t\treturn [this.text, this.buttons]\n\t\t\t},\n\t\t\t// 计算气泡和指示器的位置信息\n\t\t\ttooltipStyle() {\n\t\t\t\tconst style = {\n\t\t\t\t\t\ttransform: `translateY(${this.direction === 'top' ? '-100%' : '100%'})`,\n\t\t\t\t\t},\n\t\t\t\t\tsys = uni.$u.sys(),\n\t\t\t\t\tgetPx = uni.$u.getPx,\n\t\t\t\t\taddUnit = uni.$u.addUnit\n\t\t\t\tif (this.tooltipInfo.width / 2 > this.textInfo.left + this.textInfo.width / 2 - this.screenGap) {\n\t\t\t\t\tthis.indicatorStyle = {}\n\t\t\t\t\tstyle.left = `-${addUnit(this.textInfo.left - this.screenGap)}`\n\t\t\t\t\tthis.indicatorStyle.left = addUnit(this.textInfo.width / 2 - getPx(style.left) - this.indicatorWidth /\n\t\t\t\t\t\t2)\n\t\t\t\t} else if (this.tooltipInfo.width / 2 > sys.windowWidth - this.textInfo.right + this.textInfo.width / 2 -\n\t\t\t\t\tthis.screenGap) {\n\t\t\t\t\tthis.indicatorStyle = {}\n\t\t\t\t\tstyle.right = `-${addUnit(sys.windowWidth - this.textInfo.right - this.screenGap)}`\n\t\t\t\t\tthis.indicatorStyle.right = addUnit(this.textInfo.width / 2 - getPx(style.right) - this\n\t\t\t\t\t\t.indicatorWidth / 2)\n\t\t\t\t} else {\n\t\t\t\t\tconst left = Math.abs(this.textInfo.width / 2 - this.tooltipInfo.width / 2)\n\t\t\t\t\tstyle.left = this.textInfo.width > this.tooltipInfo.width ? addUnit(left) : -addUnit(left)\n\t\t\t\t\tthis.indicatorStyle = {}\n\t\t\t\t}\n\t\t\t\tif (this.direction === 'top') {\n\t\t\t\t\tstyle.marginTop = '-10px'\n\t\t\t\t\tthis.indicatorStyle.bottom = '-4px'\n\t\t\t\t} else {\n\t\t\t\t\tstyle.marginBottom = '-10px'\n\t\t\t\t\tthis.indicatorStyle.top = '-4px'\n\t\t\t\t}\n\t\t\t\treturn style\n\t\t\t}\n\t\t},\n\t\tmounted() {\n\t\t\tthis.init()\n\t\t},\n\t\tmethods: {\n\t\t\tinit() {\n\t\t\t\tthis.getElRect()\n\t\t\t},\n\t\t\t// 长按触发事件\n\t\t\tasync longpressHandler() {\n\t\t\t\tthis.tooltipTop = 0\n\t\t\t\tthis.showTooltip = true\n\t\t\t},\n\t\t\t// 点击透明遮罩\n\t\t\toverlayClickHandler() {\n\t\t\t\tthis.showTooltip = false\n\t\t\t},\n\t\t\t// 点击弹出按钮\n\t\t\tbtnClickHandler(index) {\n\t\t\t\tthis.showTooltip = false\n\t\t\t\t// 如果需要展示复制按钮，此处index需要加1，因为复制按钮在第一个位置\n\t\t\t\tthis.$emit('click', this.showCopy ? index + 1 : index)\n\t\t\t},\n\t\t\t// 查询内容高度\n\t\t\tqueryRect(ref) {\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\t// $uGetRect为uView自带的节点查询简化方法，详见文档介绍：https://www.uviewui.com/js/getRect.html\n\t\t\t\t// 组件内部一般用this.$uGetRect，对外的为uni.$u.getRect，二者功能一致，名称不同\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tthis.$uGetRect(`#${ref}`).then(size => {\n\t\t\t\t\t\tresolve(size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\t// nvue下，使用dom模块查询元素高度\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\n\t\t\t\treturn new Promise(resolve => {\n\t\t\t\t\tdom.getComponentRect(this.$refs[ref], res => {\n\t\t\t\t\t\tresolve(res.size)\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 元素尺寸\n\t\t\tgetElRect() {\n\t\t\t\t// 调用之前，先将指示器调整到屏幕外，方便获取尺寸\n\t\t\t\tthis.showTooltip = true\n\t\t\t\tthis.tooltipTop = -10000\n\t\t\t\tuni.$u.sleep(500).then(() => {\n\t\t\t\t\tthis.queryRect(this.tooltipId).then(size => {\n\t\t\t\t\t\tthis.tooltipInfo = size\n\t\t\t\t\t\t// 获取气泡尺寸之后，将其隐藏，为了让下次切换气泡显示与隐藏时，有淡入淡出的效果\n\t\t\t\t\t\tthis.showTooltip = false\n\t\t\t\t\t})\n\t\t\t\t\tthis.queryRect(this.textId).then(size => {\n\t\t\t\t\t\tthis.textInfo = size\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t\t// 复制文本到粘贴板\n\t\t\tsetClipboardData() {\n\t\t\t\t// 关闭组件\n\t\t\t\tthis.showTooltip = false\n\t\t\t\tthis.$emit('click', 0)\n\t\t\t\t// #ifndef H5\n\t\t\t\tuni.setClipboardData({\n\t\t\t\t\t// 优先使用copyText字段，如果没有，则默认使用text字段当做复制的内容\n\t\t\t\t\tdata: this.copyText || this.text,\n\t\t\t\t\tsuccess: () => {\n\t\t\t\t\t\tthis.showToast && uni.$u.toast('复制成功')\n\t\t\t\t\t},\n\t\t\t\t\tfail: () => {\n\t\t\t\t\t\tthis.showToast && uni.$u.toast('复制失败')\n\t\t\t\t\t},\n\t\t\t\t\tcomplete: () => {\n\t\t\t\t\t\tthis.showTooltip = false\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t\t// #endif\n\n\t\t\t\t// #ifdef H5\n\t\t\t\tlet event = window.event || e || {}\n\t\t\t\tlet clipboard = new ClipboardJS('', {\n\t\t\t\t\ttext: () => this.copyText || this.text\n\t\t\t\t})\n\t\t\t\tclipboard.on('success', (e) => {\n\t\t\t\t\tthis.showToast && uni.$u.toast('复制成功')\n\t\t\t\t\tclipboard.off('success')\n\t\t\t\t\tclipboard.off('error')\n\t\t\t\t\t// 在单页应用中，需要销毁DOM的监听\n\t\t\t\t\tclipboard.destroy()\n\t\t\t\t})\n\t\t\t\tclipboard.on('error', (e) => {\n\t\t\t\t\tthis.showToast && uni.$u.toast('复制失败')\n\t\t\t\t\tclipboard.off('success')\n\t\t\t\t\tclipboard.off('error')\n\t\t\t\t\t// 在单页应用中，需要销毁DOM的监听\n\t\t\t\t\tclipboard.destroy()\n\t\t\t\t})\n\t\t\t\tclipboard.onClick(event)\n\t\t\t\t// #endif\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\n\t.u-tooltip {\n\t\tposition: relative;\n\t\t@include flex;\n\n\t\t&__wrapper {\n\t\t\t@include flex;\n\t\t\tjustify-content: center;\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\twhite-space: nowrap;\n\t\t\t/* #endif */\n\n\t\t\t&__text {\n\t\t\t\tfont-size: 14px;\n\t\t\t}\n\n\t\t\t&__popup {\n\t\t\t\t@include flex;\n\t\t\t\tjustify-content: center;\n\n\t\t\t\t&__list {\n\t\t\t\t\tbackground-color: #060607;\n\t\t\t\t\tposition: relative;\n\t\t\t\t\tflex: 1;\n\t\t\t\t\tborder-radius: 5px;\n\t\t\t\t\tpadding: 0px 0;\n\t\t\t\t\t@include flex(row);\n\t\t\t\t\talign-items: center;\n\t\t\t\t\toverflow: hidden;\n\n\t\t\t\t\t&__btn {\n\t\t\t\t\t\tpadding: 11px 13px;\n\n\t\t\t\t\t\t&--hover {\n\t\t\t\t\t\t\tbackground-color: #58595B;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t&__text {\n\t\t\t\t\t\t\tline-height: 12px;\n\t\t\t\t\t\t\tfont-size: 13px;\n\t\t\t\t\t\t\tcolor: #FFFFFF;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t&__indicator {\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\tbackground-color: #060607;\n\t\t\t\t\twidth: 14px;\n\t\t\t\t\theight: 14px;\n\t\t\t\t\tbottom: -4px;\n\t\t\t\t\ttransform: rotate(45deg);\n\t\t\t\t\tborder-radius: 2px;\n\t\t\t\t\tz-index: -1;\n\n\t\t\t\t\t&--hover {\n\t\t\t\t\t\tbackground-color: #58595B;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tr/props.js",
    "content": "export default {\n    props: {\n\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-tr/u-tr.vue",
    "content": "<template>\n\t<view class=\"u-tr\">\n\t\t\n\t</view>\n</template>\n\n<script>\n\timport props from './props.js';\n\t/**\n\t * Tr  \n\t * @description \n\t * @tutorial url\n\t * @property {String}\n\t * @event {Function}\n\t * @example\n\t */\n\texport default {\n\t\tname: 'u-tr',\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin,props],\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=\"scss\" scoped>\n\t@import \"../../libs/css/components.scss\";\n\t\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-transition/nvue.ani-map.js",
    "content": "export default {\n    fade: {\n        enter: { opacity: 0 },\n        'enter-to': { opacity: 1 },\n        leave: { opacity: 1 },\n        'leave-to': { opacity: 0 }\n    },\n    'fade-up': {\n        enter: { opacity: 0, transform: 'translateY(100%)' },\n        'enter-to': { opacity: 1, transform: 'translateY(0)' },\n        leave: { opacity: 1, transform: 'translateY(0)' },\n        'leave-to': { opacity: 0, transform: 'translateY(100%)' }\n    },\n    'fade-down': {\n        enter: { opacity: 0, transform: 'translateY(-100%)' },\n        'enter-to': { opacity: 1, transform: 'translateY(0)' },\n        leave: { opacity: 1, transform: 'translateY(0)' },\n        'leave-to': { opacity: 0, transform: 'translateY(-100%)' }\n    },\n    'fade-left': {\n        enter: { opacity: 0, transform: 'translateX(-100%)' },\n        'enter-to': { opacity: 1, transform: 'translateY(0)' },\n        leave: { opacity: 1, transform: 'translateY(0)' },\n        'leave-to': { opacity: 0, transform: 'translateX(-100%)' }\n    },\n    'fade-right': {\n        enter: { opacity: 0, transform: 'translateX(100%)' },\n        'enter-to': { opacity: 1, transform: 'translateY(0)' },\n        leave: { opacity: 1, transform: 'translateY(0)' },\n        'leave-to': { opacity: 0, transform: 'translateX(100%)' }\n    },\n    'slide-up': {\n        enter: { transform: 'translateY(100%)' },\n        'enter-to': { transform: 'translateY(0)' },\n        leave: { transform: 'translateY(0)' },\n        'leave-to': { transform: 'translateY(100%)' }\n    },\n    'slide-down': {\n        enter: { transform: 'translateY(-100%)' },\n        'enter-to': { transform: 'translateY(0)' },\n        leave: { transform: 'translateY(0)' },\n        'leave-to': { transform: 'translateY(-100%)' }\n    },\n    'slide-left': {\n        enter: { transform: 'translateX(-100%)' },\n        'enter-to': { transform: 'translateY(0)' },\n        leave: { transform: 'translateY(0)' },\n        'leave-to': { transform: 'translateX(-100%)' }\n    },\n    'slide-right': {\n        enter: { transform: 'translateX(100%)' },\n        'enter-to': { transform: 'translateY(0)' },\n        leave: { transform: 'translateY(0)' },\n        'leave-to': { transform: 'translateX(100%)' }\n    },\n    zoom: {\n        enter: { transform: 'scale(0.95)' },\n        'enter-to': { transform: 'scale(1)' },\n        leave: { transform: 'scale(1)' },\n        'leave-to': { transform: 'scale(0.95)' }\n    },\n    'fade-zoom': {\n        enter: { opacity: 0, transform: 'scale(0.95)' },\n        'enter-to': { opacity: 1, transform: 'scale(1)' },\n        leave: { opacity: 1, transform: 'scale(1)' },\n        'leave-to': { opacity: 0, transform: 'scale(0.95)' }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-transition/props.js",
    "content": "export default {\n    props: {\n        // 是否展示组件\n        show: {\n            type: Boolean,\n            default: uni.$u.props.transition.show\n        },\n        // 使用的动画模式\n        mode: {\n            type: String,\n            default: uni.$u.props.transition.mode\n        },\n        // 动画的执行时间，单位ms\n        duration: {\n            type: [String, Number],\n            default: uni.$u.props.transition.duration\n        },\n        // 使用的动画过渡函数\n        timingFunction: {\n            type: String,\n            default: uni.$u.props.transition.timingFunction\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-transition/transition.js",
    "content": "// 定义一个一定时间后自动成功的promise，让调用nextTick方法处，进入下一个then方法\nconst nextTick = () => new Promise(resolve => setTimeout(resolve, 1000 / 50))\n// nvue动画模块实现细节抽离在外部文件\nimport animationMap from './nvue.ani-map.js'\n\n// #ifndef APP-NVUE\n// 定义类名，通过给元素动态切换类名，赋予元素一定的css动画样式\nconst getClassNames = (name) => ({\n    enter: `u-${name}-enter u-${name}-enter-active`,\n    'enter-to': `u-${name}-enter-to u-${name}-enter-active`,\n    leave: `u-${name}-leave u-${name}-leave-active`,\n    'leave-to': `u-${name}-leave-to u-${name}-leave-active`\n})\n// #endif\n\n// #ifdef APP-NVUE\n// 引入nvue(weex)的animation动画模块，文档见：\n// https://weex.apache.org/zh/docs/modules/animation.html#transition\nconst animation = uni.requireNativePlugin('animation')\nconst getStyle = (name) => animationMap[name]\n// #endif\n\nexport default {\n    methods: {\n        // 组件被点击发出事件\n        clickHandler() {\n            this.$emit('click')\n        },\n        // #ifndef APP-NVUE\n        // vue版本的组件进场处理\n         vueEnter() {\n            // 动画进入时的类名\n            const classNames = getClassNames(this.mode)\n            // 定义状态和发出动画进入前事件\n            this.status = 'enter'\n            this.$emit('beforeEnter')\n            this.inited = true\n            this.display = true\n            this.classes = classNames.enter\n            this.$nextTick(async () => {\n\t\t\t\t// #ifdef H5\n\t\t\t\tawait uni.$u.sleep(20)\n\t\t\t\t// #endif\n                // 标识动画尚未结束\n                this.$emit('enter')\n                this.transitionEnded = false\n\t\t\t\t// 组件动画进入后触发的事件\n                this.$emit('afterEnter')\n                // 赋予组件enter-to类名\n                this.classes = classNames['enter-to']\n            })\n        },\n        // 动画离场处理\n        vueLeave() {\n            // 如果不是展示状态，无需执行逻辑\n            if (!this.display) return\n            const classNames = getClassNames(this.mode)\n            // 标记离开状态和发出事件\n            this.status = 'leave'\n            this.$emit('beforeLeave')\n            // 获得类名\n            this.classes = classNames.leave\n\n            this.$nextTick(() => {\n               // 动画正在离场的状态\n               this.transitionEnded = false\n               this.$emit('leave')\n                // 组件执行动画，到了执行的执行时间后，执行一些额外处理\n                setTimeout(this.onTransitionEnd, this.duration)\n                this.classes = classNames['leave-to']\n            })\n        },\n        // #endif\n        // #ifdef APP-NVUE\n        // nvue版本动画进场\n        nvueEnter() {\n            // 获得样式的名称\n            const currentStyle = getStyle(this.mode)\n            // 组件动画状态和发出事件\n            this.status = 'enter'\n            this.$emit('beforeEnter')\n            // 展示生成组件元素\n            this.inited = true\n            this.display = true\n            // 在nvue安卓上，由于渲染速度慢，在弹窗，键盘，日历等组件中，渲染其中的内容需要时间\n            // 导致出现弹窗卡顿，这里让其一开始为透明状态，等一定时间渲染完成后，再让其隐藏起来，再让其按正常逻辑出现\n            this.viewStyle = {\n                opacity: 0\n            }\n            // 等待弹窗内容渲染完成\n            this.$nextTick(() => {\n                // 合并样式\n                this.viewStyle = currentStyle.enter\n                Promise.resolve()\n                    .then(nextTick)\n                    .then(() => {\n                        // 组件开始进入前的事件\n                        this.$emit('enter')\n                        // nvue的transition动画模块需要通过ref调用组件，注意此处的ref不同于vue的this.$refs['u-transition']用法\n                        animation.transition(this.$refs['u-transition'].ref, {\n                            styles: currentStyle['enter-to'],\n                            duration: this.duration,\n                            timingFunction: this.timingFunction,\n                            needLayout: false,\n                            delay: 0\n                        }, () => {\n                            // 动画执行完毕，发出事件\n                            this.$emit('afterEnter')\n                        })\n                    })\n                    .catch(() => {})\n            })\n        },\n        nvueLeave() {\n            if (!this.display) {\n                return\n            }\n            const currentStyle = getStyle(this.mode)\n            // 定义状态和事件\n            this.status = 'leave'\n            this.$emit('beforeLeave')\n            // 合并样式\n            this.viewStyle = currentStyle.leave\n            // 放到promise中处理执行过程\n            Promise.resolve()\n                .then(nextTick) // 等待几十ms\n                .then(() => {\n                    this.transitionEnded = false\n                    // 动画正在离场的状态\n                    this.$emit('leave')\n                    animation.transition(this.$refs['u-transition'].ref, {\n                        styles: currentStyle['leave-to'],\n                        duration: this.duration,\n                        timingFunction: this.timingFunction,\n                        needLayout: false,\n                        delay: 0\n                    }, () => {\n                        this.onTransitionEnd()\n                    })\n                })\n                .catch(() => {})\n        },\n        // #endif\n        // 完成过渡后触发\n        onTransitionEnd() {\n            // 如果已经是结束的状态，无需再处理\n            if (this.transitionEnded) return\n            this.transitionEnded = true\n            // 发出组件动画执行后的事件\n            this.$emit(this.status === 'leave' ? 'afterLeave' : 'afterEnter')\n            if (!this.show && this.display) {\n                this.display = false\n                this.inited = false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-transition/u-transition.vue",
    "content": "<template>\n\t<view\n\t\tv-if=\"inited\"\n\t\tclass=\"u-transition\"\n\t\tref=\"u-transition\"\n\t\t@tap=\"clickHandler\"\n\t\t:class=\"classes\"\n\t\t:style=\"[mergeStyle]\"\n\t\t@touchmove=\"noop\"\n\t>\n\t\t<slot />\n\t</view>\n</template>\n\n<script>\nimport props from './props.js';\n// 组件的methods方法，由于内容较长，写在外部文件中通过mixin引入\nimport transition from \"./transition.js\";\n/**\n * transition  动画组件\n * @description\n * @tutorial\n * @property {String}\t\t\tshow\t\t\t是否展示组件 （默认 false ）\n * @property {String}\t\t\tmode\t\t\t使用的动画模式 （默认 'fade' ）\n * @property {String | Number}\tduration\t\t动画的执行时间，单位ms （默认 '300' ）\n * @property {String}\t\t\ttimingFunction\t使用的动画过渡函数 （默认 'ease-out' ）\n * @property {Object}\t\t\tcustomStyle\t\t自定义样式\n * @event {Function} before-enter\t进入前触发\n * @event {Function} enter\t\t\t进入中触发\n * @event {Function} after-enter\t进入后触发\n * @event {Function} before-leave\t离开前触发\n * @event {Function} leave\t\t\t离开中触发\n * @event {Function} after-leave\t离开后触发\n * @example\n */\nexport default {\n\tname: 'u-transition',\n\tdata() {\n\t\treturn {\n\t\t\tinited: false, // 是否显示/隐藏组件\n\t\t\tviewStyle: {}, // 组件内部的样式\n\t\t\tstatus: '', // 记录组件动画的状态\n\t\t\ttransitionEnded: false, // 组件是否结束的标记\n\t\t\tdisplay: false, // 组件是否展示\n\t\t\tclasses: '', // 应用的类名\n\t\t}\n\t},\n\tcomputed: {\n\t    mergeStyle() {\n\t        const { viewStyle, customStyle } = this\n\t        return {\n\t            // #ifndef APP-NVUE\n\t            transitionDuration: `${this.duration}ms`,\n\t            // display: `${this.display ? '' : 'none'}`,\n\t\t\t\ttransitionTimingFunction: this.timingFunction,\n\t            // #endif\n\t\t\t\t// 避免自定义样式影响到动画属性，所以写在viewStyle前面\n\t            ...uni.$u.addStyle(customStyle),\n\t            ...viewStyle\n\t        }\n\t    }\n\t},\n\t// 将mixin挂在到组件中，uni.$u.mixin实际上为一个vue格式对象\n\tmixins: [uni.$u.mpMixin, uni.$u.mixin, transition, props],\n\twatch: {\n\t\tshow: {\n\t\t\thandler(newVal) {\n\t\t\t\t// vue和nvue分别执行不同的方法\n\t\t\t\t// #ifdef APP-NVUE\n\t\t\t\tnewVal ? this.nvueEnter() : this.nvueLeave()\n\t\t\t\t// #endif\n\t\t\t\t// #ifndef APP-NVUE\n\t\t\t\tnewVal ? this.vueEnter() : this.vueLeave()\n\t\t\t\t// #endif\n\t\t\t},\n\t\t\t// 表示同时监听初始化时的props的show的意思\n\t\t\timmediate: true\n\t\t}\n\t}\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '../../libs/css/components.scss';\n\n/* #ifndef APP-NVUE */\n// vue版本动画相关的样式抽离在外部文件\n@import './vue.ani-style.scss';\n/* #endif */\n\n.u-transition {}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-transition/vue.ani-style.scss",
    "content": "/**\n * vue版本动画内置的动画模式有如下：\n * fade：淡入\n * zoom：缩放\n * fade-zoom：缩放淡入\n * fade-up：上滑淡入\n * fade-down：下滑淡入\n * fade-left：左滑淡入\n * fade-right：右滑淡入\n * slide-up：上滑进入\n * slide-down：下滑进入\n * slide-left：左滑进入\n * slide-right：右滑进入\n */\n\n$u-zoom-scale: scale(0.95);\n\n.u-fade-enter-active,\n.u-fade-leave-active {\n\ttransition-property: opacity;\n}\n\n.u-fade-enter,\n.u-fade-leave-to {\n\topacity: 0\n}\n\n.u-fade-zoom-enter,\n.u-fade-zoom-leave-to {\n\ttransform: $u-zoom-scale;\n\topacity: 0;\n}\n\n.u-fade-zoom-enter-active,\n.u-fade-zoom-leave-active {\n\ttransition-property: transform, opacity;\n}\n\n.u-fade-down-enter-active,\n.u-fade-down-leave-active,\n.u-fade-left-enter-active,\n.u-fade-left-leave-active,\n.u-fade-right-enter-active,\n.u-fade-right-leave-active,\n.u-fade-up-enter-active,\n.u-fade-up-leave-active {\n\ttransition-property: opacity, transform;\n}\n\n.u-fade-up-enter,\n.u-fade-up-leave-to {\n\ttransform: translate3d(0, 100%, 0);\n\topacity: 0\n}\n\n.u-fade-down-enter,\n.u-fade-down-leave-to {\n\ttransform: translate3d(0, -100%, 0);\n\topacity: 0\n}\n\n.u-fade-left-enter,\n.u-fade-left-leave-to {\n\ttransform: translate3d(-100%, 0, 0);\n\topacity: 0\n}\n\n.u-fade-right-enter,\n.u-fade-right-leave-to {\n\ttransform: translate3d(100%, 0, 0);\n\topacity: 0\n}\n\n.u-slide-down-enter-active,\n.u-slide-down-leave-active,\n.u-slide-left-enter-active,\n.u-slide-left-leave-active,\n.u-slide-right-enter-active,\n.u-slide-right-leave-active,\n.u-slide-up-enter-active,\n.u-slide-up-leave-active {\n\ttransition-property: transform;\n}\n\n.u-slide-up-enter,\n.u-slide-up-leave-to {\n\ttransform: translate3d(0, 100%, 0)\n}\n\n.u-slide-down-enter,\n.u-slide-down-leave-to {\n\ttransform: translate3d(0, -100%, 0)\n}\n\n.u-slide-left-enter,\n.u-slide-left-leave-to {\n\ttransform: translate3d(-100%, 0, 0)\n}\n\n.u-slide-right-enter,\n.u-slide-right-leave-to {\n\ttransform: translate3d(100%, 0, 0)\n}\n\n.u-zoom-enter-active,\n.u-zoom-leave-active {\n\ttransition-property: transform\n}\n\n.u-zoom-enter,\n.u-zoom-leave-to {\n\ttransform: $u-zoom-scale\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-upload/mixin.js",
    "content": "export default {\n    watch: {\n        // 监听accept的变化，判断是否符合个平台要求\n        // 只有微信小程序才支持选择媒体，文件类型，所以这里做一个判断提示\n        accept: {\n            immediate: true,\n            handler(val) {\n                // #ifndef MP-WEIXIN\n                if (val === 'all' || val === 'media') {\n                    uni.$u.error('只有微信小程序才支持把accept配置为all、media之一')\n                }\n                // #endif\n                // #ifndef H5 || MP-WEIXIN\n                if (val === 'file') {\n                    uni.$u.error('只有微信小程序和H5(HX2.9.9)才支持把accept配置为file')\n                }\n                // #endif\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-upload/props.js",
    "content": "export default {\n    props: {\n        // 接受的文件类型, 可选值为all media image file video\n        accept: {\n            type: String,\n            default: uni.$u.props.upload.accept\n        },\n        // \t图片或视频拾取模式，当accept为image类型时设置capture可选额外camera可以直接调起摄像头\n        capture: {\n            type: [String, Array],\n            default: uni.$u.props.upload.capture\n        },\n        // 当accept为video时生效，是否压缩视频，默认为true\n        compressed: {\n            type: Boolean,\n            default: uni.$u.props.upload.compressed\n        },\n        // 当accept为video时生效，可选值为back或front\n        camera: {\n            type: String,\n            default: uni.$u.props.upload.camera\n        },\n        // 当accept为video时生效，拍摄视频最长拍摄时间，单位秒\n        maxDuration: {\n            type: Number,\n            default: uni.$u.props.upload.maxDuration\n        },\n        // 上传区域的图标，只能内置图标\n        uploadIcon: {\n            type: String,\n            default: uni.$u.props.upload.uploadIcon\n        },\n        // 上传区域的图标的颜色，默认\n        uploadIconColor: {\n            type: String,\n            default: uni.$u.props.upload.uploadIconColor\n        },\n        // 是否开启文件读取前事件\n        useBeforeRead: {\n            type: Boolean,\n            default: uni.$u.props.upload.useBeforeRead\n        },\n        // 读取后的处理函数\n        afterRead: {\n            type: Function,\n            default: null\n        },\n        // 读取前的处理函数\n        beforeRead: {\n            type: Function,\n            default: null\n        },\n        // 是否显示组件自带的图片预览功能\n        previewFullImage: {\n            type: Boolean,\n            default: uni.$u.props.upload.previewFullImage\n        },\n        // 最大上传数量\n        maxCount: {\n            type: [String, Number],\n            default: uni.$u.props.upload.maxCount\n        },\n        // 是否启用\n        disabled: {\n            type: Boolean,\n            default: uni.$u.props.upload.disabled\n        },\n        // 预览上传的图片时的裁剪模式，和image组件mode属性一致\n        imageMode: {\n            type: String,\n            default: uni.$u.props.upload.imageMode\n        },\n        // 标识符，可以在回调函数的第二项参数中获取\n        name: {\n            type: String,\n            default: uni.$u.props.upload.name\n        },\n        // 所选的图片的尺寸, 可选值为original compressed\n        sizeType: {\n            type: Array,\n            default: uni.$u.props.upload.sizeType\n        },\n        // 是否开启图片多选，部分安卓机型不支持\n        multiple: {\n            type: Boolean,\n            default: uni.$u.props.upload.multiple\n        },\n        // 是否展示删除按钮\n        deletable: {\n            type: Boolean,\n            default: uni.$u.props.upload.deletable\n        },\n        // 文件大小限制，单位为byte\n        maxSize: {\n            type: [String, Number],\n            default: uni.$u.props.upload.maxSize\n        },\n        // 显示已上传的文件列表\n        fileList: {\n            type: Array,\n            default: uni.$u.props.upload.fileList\n        },\n        // 上传区域的提示文字\n        uploadText: {\n            type: String,\n            default: uni.$u.props.upload.uploadText\n        },\n        // 内部预览图片区域和选择图片按钮的区域宽度\n        width: {\n            type: [String, Number],\n            default: uni.$u.props.upload.width\n        },\n        // 内部预览图片区域和选择图片按钮的区域高度\n        height: {\n            type: [String, Number],\n            default: uni.$u.props.upload.height\n        },\n        // 是否在上传完成后展示预览图\n        previewImage: {\n            type: Boolean,\n            default: uni.$u.props.upload.previewImage\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-upload/u-upload.vue",
    "content": "<template>\n\t<view class=\"u-upload\" :style=\"[$u.addStyle(customStyle)]\">\n\t\t<view class=\"u-upload__wrap\" >\n\t\t\t<template v-if=\"previewImage\">\n\t\t\t\t<view\n\t\t\t\t    class=\"u-upload__wrap__preview\"\n\t\t\t\t    v-for=\"(item, index) in lists\"\n\t\t\t\t    :key=\"index\"\n\t\t\t\t>\n\t\t\t\t\t<image\n\t\t\t\t\t    v-if=\"item.isImage || (item.type && item.type === 'image')\"\n\t\t\t\t\t    :src=\"item.thumb || item.url\"\n\t\t\t\t\t    :mode=\"imageMode\"\n\t\t\t\t\t    class=\"u-upload__wrap__preview__image\"\n\t\t\t\t\t    @tap=\"onPreviewImage(item)\"\n\t\t\t\t\t\t:style=\"[{\n\t\t\t\t\t\t\twidth: $u.addUnit(width),\n\t\t\t\t\t\t\theight: $u.addUnit(height)\n\t\t\t\t\t\t}]\"\n\t\t\t\t\t/>\n\t\t\t\t\t<view\n\t\t\t\t\t    v-else\n\t\t\t\t\t    class=\"u-upload__wrap__preview__other\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t    color=\"#80CBF9\"\n\t\t\t\t\t\t    size=\"26\"\n\t\t\t\t\t\t    :name=\"item.isVideo || (item.type && item.type === 'video') ? 'movie' : 'folder'\"\n\t\t\t\t\t\t></u-icon>\n\t\t\t\t\t\t<text class=\"u-upload__wrap__preview__other__text\">{{item.isVideo || (item.type && item.type === 'video') ? '视频' : '文件'}}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view\n\t\t\t\t\t    class=\"u-upload__status\"\n\t\t\t\t\t    v-if=\"item.status === 'uploading' || item.status === 'failed'\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<view class=\"u-upload__status__icon\">\n\t\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t\t    v-if=\"item.status === 'failed'\"\n\t\t\t\t\t\t\t    name=\"close-circle\"\n\t\t\t\t\t\t\t    color=\"#ffffff\"\n\t\t\t\t\t\t\t    size=\"25\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<u-loading-icon\n\t\t\t\t\t\t\t    size=\"22\"\n\t\t\t\t\t\t\t    mode=\"circle\"\n\t\t\t\t\t\t\t    color=\"#ffffff\"\n\t\t\t\t\t\t\t    v-else\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<text\n\t\t\t\t\t\t    v-if=\"item.message\"\n\t\t\t\t\t\t    class=\"u-upload__status__message\"\n\t\t\t\t\t\t>{{ item.message }}</text>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view\n\t\t\t\t\t    class=\"u-upload__deletable\"\n\t\t\t\t\t    v-if=\"item.status !== 'uploading' && (deletable || item.deletable)\"\n\t\t\t\t\t    @tap.stop=\"deleteItem(index)\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<view class=\"u-upload__deletable__icon\">\n\t\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t\t    name=\"close\"\n\t\t\t\t\t\t\t    color=\"#ffffff\"\n\t\t\t\t\t\t\t    size=\"10\"\n\t\t\t\t\t\t\t></u-icon>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view\n\t\t\t\t\t    class=\"u-upload__success\"\n\t\t\t\t\t    v-if=\"item.status === 'success'\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<!-- #ifdef APP-NVUE -->\n\t\t\t\t\t\t<image\n\t\t\t\t\t\t    :src=\"successIcon\"\n\t\t\t\t\t\t    class=\"u-upload__success__icon\"\n\t\t\t\t\t\t></image>\n\t\t\t\t\t\t<!-- #endif -->\n\t\t\t\t\t\t<!-- #ifndef APP-NVUE -->\n\t\t\t\t\t\t<view class=\"u-upload__success__icon\">\n\t\t\t\t\t\t\t<u-icon\n\t\t\t\t\t\t\t    name=\"checkmark\"\n\t\t\t\t\t\t\t    color=\"#ffffff\"\n\t\t\t\t\t\t\t    size=\"12\"\n\t\t\t\t\t\t\t></u-icon>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<!-- #endif -->\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t\n\t\t\t</template>\n\t\t\t\n\t\t\t<template v-if=\"isInCount\">\n\t\t\t\t<view\n\t\t\t\t    v-if=\"$slots.default || $slots.$default\"\n\t\t\t\t    @tap=\"chooseFile\"\n\t\t\t\t>\n\t\t\t\t\t<slot />\n\t\t\t\t</view>\n\t\t\t\t<view\n\t\t\t\t    v-else\n\t\t\t\t    class=\"u-upload__button\"\n\t\t\t\t    :hover-class=\"!disabled ? 'u-upload__button--hover' : ''\"\n\t\t\t\t    hover-stay-time=\"150\"\n\t\t\t\t    @tap=\"chooseFile\"\n\t\t\t\t    :class=\"[disabled && 'u-upload__button--disabled']\"\n\t\t\t\t\t:style=\"[{\n\t\t\t\t\t\twidth: $u.addUnit(width),\n\t\t\t\t\t\theight: $u.addUnit(height)\n\t\t\t\t\t}]\"\n\t\t\t\t>\n\t\t\t\t\t<u-icon\n\t\t\t\t\t    :name=\"uploadIcon\"\n\t\t\t\t\t    size=\"26\"\n\t\t\t\t\t    :color=\"uploadIconColor\"\n\t\t\t\t\t></u-icon>\n\t\t\t\t\t<text\n\t\t\t\t\t    v-if=\"uploadText\"\n\t\t\t\t\t    class=\"u-upload__button__text\"\n\t\t\t\t\t>{{ uploadText }}</text>\n\t\t\t\t</view>\n\t\t\t</template>\n\t\t</view>\n\n\t</view>\n</template>\n\n<script>\n\timport {\n\t\tchooseFile\n\t} from './utils';\n\timport mixin from './mixin.js';\n\timport props from './props.js';\n\n\t/**\n\t * upload 上传\n\t * @description 该组件用于上传图片场景\n\t * @tutorial https://uviewui.com/components/upload.html\n\t * @property {String}\t\t\taccept\t\t\t\t接受的文件类型, 可选值为all media image file video （默认 'image' ）\n\t * @property {String | Array}\tcapture\t\t\t\t图片或视频拾取模式，当accept为image类型时设置capture可选额外camera可以直接调起摄像头（默认 ['album', 'camera'] ）\n\t * @property {Boolean}\t\t\tcompressed\t\t\t当accept为video时生效，是否压缩视频，默认为true（默认 true ）\n\t * @property {String}\t\t\tcamera\t\t\t\t当accept为video时生效，可选值为back或front（默认 'back' ）\n\t * @property {Number}\t\t\tmaxDuration\t\t\t当accept为video时生效，拍摄视频最长拍摄时间，单位秒（默认 60 ）\n\t * @property {String}\t\t\tuploadIcon\t\t\t上传区域的图标，只能内置图标（默认 'camera-fill' ）\n\t * @property {String}\t\t\tuploadIconColor\t\t上传区域的图标的字体颜色，只能内置图标（默认 #D3D4D6 ）\n\t * @property {Boolean}\t\t\tuseBeforeRead\t\t是否开启文件读取前事件（默认 false ）\n\t * @property {Boolean}\t\t\tpreviewFullImage\t是否显示组件自带的图片预览功能（默认 true ）\n\t * @property {String | Number}\tmaxCount\t\t\t最大上传数量（默认 52 ）\n\t * @property {Boolean}\t\t\tdisabled\t\t\t是否启用（默认 false ）\n\t * @property {String}\t\t\timageMode\t\t\t预览上传的图片时的裁剪模式，和image组件mode属性一致（默认 'aspectFill' ）\n\t * @property {String}\t\t\tname\t\t\t\t标识符，可以在回调函数的第二项参数中获取\n\t * @property {Array}\t\t\tsizeType\t\t\t所选的图片的尺寸, 可选值为original compressed（默认 ['original', 'compressed'] ）\n\t * @property {Boolean}\t\t\tmultiple\t\t\t是否开启图片多选，部分安卓机型不支持 （默认 false ）\n\t * @property {Boolean}\t\t\tdeletable\t\t\t是否展示删除按钮（默认 true ）\n\t * @property {String | Number}\tmaxSize\t\t\t\t文件大小限制，单位为byte （默认 Number.MAX_VALUE ）\n\t * @property {Array}\t\t\tfileList\t\t\t显示已上传的文件列表\n\t * @property {String}\t\t\tuploadText\t\t\t上传区域的提示文字\n\t * @property {String | Number}\twidth\t\t\t\t内部预览图片区域和选择图片按钮的区域宽度（默认 80 ）\n\t * @property {String | Number}\theight\t\t\t\t内部预览图片区域和选择图片按钮的区域高度（默认 80 ）\n\t * @property {Object}\t\t\tcustomStyle\t\t\t组件的样式，对象形式\n\t * @event {Function} afterRead\t\t读取后的处理函数\n\t * @event {Function} beforeRead\t\t读取前的处理函数\n\t * @event {Function} oversize\t\t文件超出大小限制\n\t * @event {Function} clickPreview\t点击预览图片\n\t * @event {Function} delete \t\t删除图片\n\t * @example <u-upload :action=\"action\" :fileList=\"fileList\" ></u-upload>\n\t */\n\texport default {\n\t\tname: \"u-upload\",\n\t\tmixins: [uni.$u.mpMixin, uni.$u.mixin, mixin,props],\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t// #ifdef APP-NVUE\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=',\n\t\t\t\t// #endif\n\t\t\t\tlists: [],\n\t\t\t\tisInCount: true,\n\t\t\t}\n\t\t},\n\t\twatch: {\n\t\t\t// 监听文件列表的变化，重新整理内部数据\n\t\t\tfileList: {\n\t\t\t\timmediate: true,\n\t\t\t\thandler() {\n\t\t\t\t\tthis.formatFileList()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\tmethods: {\n\t\t\tformatFileList() {\n\t\t\t\tconst {\n\t\t\t\t\tfileList = [], maxCount\n\t\t\t\t} = this;\n\t\t\t\tconst lists = fileList.map((item) =>\n\t\t\t\t\tObject.assign(Object.assign({}, item), {\n\t\t\t\t\t\t// 如果item.url为本地选择的blob文件的话，无法判断其为video还是image，此处优先通过accept做判断处理\n\t\t\t\t\t\tisImage: this.accept === 'image' || uni.$u.test.image(item.url || item.thumb),\n\t\t\t\t\t\tisVideo: this.accept === 'video' || uni.$u.test.video(item.url || item.thumb),\n\t\t\t\t\t\tdeletable: typeof(item.deletable) === 'boolean' ? item.deletable : this.deletable,\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\tthis.lists = lists\n\t\t\t\tthis.isInCount = lists.length < maxCount\n\t\t\t},\n\t\t\tchooseFile() {\n\t\t\t\tconst {\n\t\t\t\t\tmaxCount,\n\t\t\t\t\tmultiple,\n\t\t\t\t\tlists,\n\t\t\t\t\tdisabled\n\t\t\t\t} = this;\n\t\t\t\tif (disabled) return;\n\t\t\t\t// 如果用户传入的是字符串，需要格式化成数组\n\t\t\t\tlet capture;\n\t\t\t\ttry {\n\t\t\t\t\tcapture = uni.$u.test.array(this.capture) ? this.capture : this.capture.split(',');\n\t\t\t\t}catch(e) {\n\t\t\t\t\tcapture = [];\n\t\t\t\t}\n\t\t\t\tchooseFile(\n\t\t\t\t\t\tObject.assign({\n\t\t\t\t\t\t\taccept: this.accept,\n\t\t\t\t\t\t\tmultiple: this.multiple,\n\t\t\t\t\t\t\tcapture: capture,\n\t\t\t\t\t\t\tcompressed: this.compressed,\n\t\t\t\t\t\t\tmaxDuration: this.maxDuration,\n\t\t\t\t\t\t\tsizeType: this.sizeType,\n\t\t\t\t\t\t\tcamera: this.camera,\n\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\tmaxCount: maxCount - lists.length,\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t\t.then((res) => {\n\t\t\t\t\t\tthis.onBeforeRead(multiple ? res : res[0]);\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error) => {\n\t\t\t\t\t\tthis.$emit('error', error);\n\t\t\t\t\t});\n\t\t\t},\n\t\t\t// 文件读取之前\n\t\t\tonBeforeRead(file) {\n\t\t\t\tconst {\n\t\t\t\t\tbeforeRead,\n\t\t\t\t\tuseBeforeRead,\n\t\t\t\t} = this;\n\t\t\t\tlet res = true\n\t\t\t\t// beforeRead是否为一个方法\n\t\t\t\tif (uni.$u.test.func(beforeRead)) {\n\t\t\t\t\t// 如果用户定义了此方法，则去执行此方法，并传入读取的文件回调\n\t\t\t\t\tres = beforeRead(file, this.getDetail());\n\t\t\t\t}\n\t\t\t\tif (useBeforeRead) {\n\t\t\t\t\tres = new Promise((resolve, reject) => {\n\t\t\t\t\t\tthis.$emit(\n\t\t\t\t\t\t\t'beforeRead',\n\t\t\t\t\t\t\tObject.assign(Object.assign({\n\t\t\t\t\t\t\t\tfile\n\t\t\t\t\t\t\t}, this.getDetail()), {\n\t\t\t\t\t\t\t\tcallback: (ok) => {\n\t\t\t\t\t\t\t\t\tok ? resolve() : reject();\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\tif (!res) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (uni.$u.test.promise(res)) {\n\t\t\t\t\tres.then((data) => this.onAfterRead(data || file));\n\t\t\t\t} else {\n\t\t\t\t\tthis.onAfterRead(file);\n\t\t\t\t}\n\t\t\t},\n\t\t\tgetDetail(index) {\n\t\t\t\treturn {\n\t\t\t\t\tname: this.name,\n\t\t\t\t\tindex: index == null ? this.fileList.length : index,\n\t\t\t\t};\n\t\t\t},\n\t\t\tonAfterRead(file) {\n\t\t\t\tconst {\n\t\t\t\t\tmaxSize,\n\t\t\t\t\tafterRead\n\t\t\t\t} = this;\n\t\t\t\tconst oversize = Array.isArray(file) ?\n\t\t\t\t\tfile.some((item) => item.size > maxSize) :\n\t\t\t\t\tfile.size > maxSize;\n\t\t\t\tif (oversize) {\n\t\t\t\t\tthis.$emit('oversize', Object.assign({\n\t\t\t\t\t\tfile\n\t\t\t\t\t}, this.getDetail()));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (typeof afterRead === 'function') {\n\t\t\t\t\tafterRead(file, this.getDetail());\n\t\t\t\t}\n\t\t\t\tthis.$emit('afterRead', Object.assign({\n\t\t\t\t\tfile\n\t\t\t\t}, this.getDetail()));\n\t\t\t},\n\t\t\tdeleteItem(index) {\n\t\t\t\tthis.$emit(\n\t\t\t\t\t'delete',\n\t\t\t\t\tObject.assign(Object.assign({}, this.getDetail(index)), {\n\t\t\t\t\t\tfile: this.fileList[index],\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t},\n\t\t\t// 预览图片\n\t\t\tonPreviewImage(item) {\n\t\t\t\tif (!item.isImage || !this.previewFullImage) return\n\t\t\t\tuni.previewImage({\n\t\t\t\t\t// 先filter找出为图片的item，再返回filter结果中的图片url\n\t\t\t\t\turls: this.lists.filter((item) => this.accept === 'image' || uni.$u.test.image(item.url || item.thumb)).map((item) => item.url || item.thumb),\n\t\t\t\t\tcurrent: item.url || item.thumb,\n\t\t\t\t\tfail() {\n\t\t\t\t\t\tuni.$u.toast('预览图片失败')\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t\tonPreviewVideo(event) {\n\t\t\t\tif (!this.data.previewFullImage) return;\n\t\t\t\tconst {\n\t\t\t\t\tindex\n\t\t\t\t} = event.currentTarget.dataset;\n\t\t\t\tconst {\n\t\t\t\t\tlists\n\t\t\t\t} = this.data;\n\t\t\t\twx.previewMedia({\n\t\t\t\t\tsources: lists\n\t\t\t\t\t\t.filter((item) => isVideoFile(item))\n\t\t\t\t\t\t.map((item) =>\n\t\t\t\t\t\t\tObject.assign(Object.assign({}, item), {\n\t\t\t\t\t\t\t\ttype: 'video'\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t),\n\t\t\t\t\tcurrent: index,\n\t\t\t\t\tfail() {\n\t\t\t\t\t\tuni.$u.toast('预览视频失败')\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t},\n\t\t\tonClickPreview(event) {\n\t\t\t\tconst {\n\t\t\t\t\tindex\n\t\t\t\t} = event.currentTarget.dataset;\n\t\t\t\tconst item = this.data.lists[index];\n\t\t\t\tthis.$emit(\n\t\t\t\t\t'clickPreview',\n\t\t\t\t\tObject.assign(Object.assign({}, item), this.getDetail(index))\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n</script>\n\n<style lang=\"scss\" scoped>\n\t@import '../../libs/css/components.scss';\n\t$u-upload-preview-border-radius: 2px !default;\n\t$u-upload-preview-margin: 0 8px 8px 0 !default;\n\t$u-upload-image-width:80px !default;\n\t$u-upload-image-height:$u-upload-image-width;\n\t$u-upload-other-bgColor: rgb(242, 242, 242) !default;\n\t$u-upload-other-flex:1 !default;\n\t$u-upload-text-font-size:11px !default;\n\t$u-upload-text-color:$u-tips-color !default;\n\t$u-upload-text-margin-top:2px !default;\n\t$u-upload-deletable-right:0 !default;\n\t$u-upload-deletable-top:0 !default;\n\t$u-upload-deletable-bgColor:rgb(55, 55, 55) !default;\n\t$u-upload-deletable-height:14px !default;\n\t$u-upload-deletable-width:$u-upload-deletable-height;\n\t$u-upload-deletable-boder-bottom-left-radius:100px !default;\n\t$u-upload-deletable-zIndex:3 !default;\n\t$u-upload-success-bottom:0 !default;\n\t$u-upload-success-right:0 !default;\n\t$u-upload-success-border-style:solid !default;\n\t$u-upload-success-border-top-color:transparent !default;\n\t$u-upload-success-border-left-color:transparent !default;\n\t$u-upload-success-border-bottom-color: $u-success !default;\n\t$u-upload-success-border-right-color:$u-upload-success-border-bottom-color;\n\t$u-upload-success-border-width:9px !default;\n\t$u-upload-icon-top:0px !default;\n\t$u-upload-icon-right:0px !default;\n\t$u-upload-icon-h5-top:1px !default;\n\t$u-upload-icon-h5-right:0 !default;\n\t$u-upload-icon-width:16px !default;\n\t$u-upload-icon-height:$u-upload-icon-width;\n\t$u-upload-success-icon-bottom:-10px !default;\n\t$u-upload-success-icon-right:-10px !default;\n\t$u-upload-status-right:0 !default;\n\t$u-upload-status-left:0 !default;\n\t$u-upload-status-bottom:0 !default;\n\t$u-upload-status-top:0 !default;\n\t$u-upload-status-bgColor:rgba(0, 0, 0, 0.5) !default;\n\t$u-upload-status-icon-Zindex:1 !default;\n\t$u-upload-message-font-size:12px !default;\n\t$u-upload-message-color:#FFFFFF !default;\n\t$u-upload-message-margin-top:5px !default;\n\t$u-upload-button-width:80px !default;\n\t$u-upload-button-height:$u-upload-button-width;\n\t$u-upload-button-bgColor:rgb(244, 245, 247) !default;\n\t$u-upload-button-border-radius:2px !default;\n\t$u-upload-botton-margin: 0 8px 8px 0 !default;\n\t$u-upload-text-font-size:11px !default;\n\t$u-upload-text-color:$u-tips-color !default;\n\t$u-upload-text-margin-top: 2px !default;\n\t$u-upload-hover-bgColor:rgb(230, 231, 233) !default;\n\t$u-upload-disabled-opacity:.5 !default;\n\n\t.u-upload {\n\t\t@include flex(column);\n\t\tflex: 1;\n\n\t\t&__wrap {\n\t\t\t@include flex;\n\t\t\tflex-wrap: wrap;\n\t\t\tflex: 1;\n\n\t\t\t&__preview {\n\t\t\t\tborder-radius: $u-upload-preview-border-radius;\n\t\t\t\tmargin: $u-upload-preview-margin;\n\t\t\t\tposition: relative;\n\t\t\t\toverflow: hidden;\n\t\t\t\t@include flex;\n\n\t\t\t\t&__image {\n\t\t\t\t\twidth: $u-upload-image-width;\n\t\t\t\t\theight: $u-upload-image-height;\n\t\t\t\t}\n\n\t\t\t\t&__other {\n\t\t\t\t\twidth: $u-upload-image-width;\n\t\t\t\t\theight: $u-upload-image-height;\n\t\t\t\t\tbackground-color: $u-upload-other-bgColor;\n\t\t\t\t\tflex: $u-upload-other-flex;\n\t\t\t\t\t@include flex(column);\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t\talign-items: center;\n\n\t\t\t\t\t&__text {\n\t\t\t\t\t\tfont-size: $u-upload-text-font-size;\n\t\t\t\t\t\tcolor: $u-upload-text-color;\n\t\t\t\t\t\tmargin-top: $u-upload-text-margin-top;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t&__deletable {\n\t\t\tposition: absolute;\n\t\t\ttop: $u-upload-deletable-top;\n\t\t\tright: $u-upload-deletable-right;\n\t\t\tbackground-color: $u-upload-deletable-bgColor;\n\t\t\theight: $u-upload-deletable-height;\n\t\t\twidth: $u-upload-deletable-width;\n\t\t\t@include flex;\n\t\t\tborder-bottom-left-radius: $u-upload-deletable-boder-bottom-left-radius;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tz-index: $u-upload-deletable-zIndex;\n\n\t\t\t&__icon {\n\t\t\t\tposition: absolute;\n\t\t\t\ttransform: scale(0.7);\n\t\t\t\ttop: $u-upload-icon-top;\n\t\t\t\tright: $u-upload-icon-right;\n\t\t\t\t/* #ifdef H5 */\n\t\t\t\ttop: $u-upload-icon-h5-top;\n\t\t\t\tright: $u-upload-icon-h5-right;\n\t\t\t\t/* #endif */\n\t\t\t}\n\t\t}\n\n\t\t&__success {\n\t\t\tposition: absolute;\n\t\t\tbottom: $u-upload-success-bottom;\n\t\t\tright: $u-upload-success-right;\n\t\t\t@include flex;\n\t\t\t// 由于weex(nvue)为阿里巴巴的KPI(部门业绩考核)的laji产物，不支持css绘制三角形\n\t\t\t// 所以在nvue下使用图片，非nvue下使用css实现\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tborder-style: $u-upload-success-border-style;\n\t\t\tborder-top-color: $u-upload-success-border-top-color;\n\t\t\tborder-left-color: $u-upload-success-border-left-color;\n\t\t\tborder-bottom-color: $u-upload-success-border-bottom-color;\n\t\t\tborder-right-color: $u-upload-success-border-right-color;\n\t\t\tborder-width: $u-upload-success-border-width;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\t/* #endif */\n\n\t\t\t&__icon {\n\t\t\t\t/* #ifndef APP-NVUE */\n\t\t\t\tposition: absolute;\n\t\t\t\ttransform: scale(0.7);\n\t\t\t\tbottom: $u-upload-success-icon-bottom;\n\t\t\t\tright: $u-upload-success-icon-right;\n\t\t\t\t/* #endif */\n\t\t\t\t/* #ifdef APP-NVUE */\n\t\t\t\twidth: $u-upload-icon-width;\n\t\t\t\theight: $u-upload-icon-height;\n\t\t\t\t/* #endif */\n\t\t\t}\n\t\t}\n\n\t\t&__status {\n\t\t\tposition: absolute;\n\t\t\ttop: $u-upload-status-top;\n\t\t\tbottom: $u-upload-status-bottom;\n\t\t\tleft: $u-upload-status-left;\n\t\t\tright: $u-upload-status-right;\n\t\t\tbackground-color: $u-upload-status-bgColor;\n\t\t\t@include flex(column);\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\n\t\t\t&__icon {\n\t\t\t\tposition: relative;\n\t\t\t\tz-index: $u-upload-status-icon-Zindex;\n\t\t\t}\n\n\t\t\t&__message {\n\t\t\t\tfont-size: $u-upload-message-font-size;\n\t\t\t\tcolor: $u-upload-message-color;\n\t\t\t\tmargin-top: $u-upload-message-margin-top;\n\t\t\t}\n\t\t}\n\n\t\t&__button {\n\t\t\t@include flex(column);\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\twidth: $u-upload-button-width;\n\t\t\theight: $u-upload-button-height;\n\t\t\tbackground-color: $u-upload-button-bgColor;\n\t\t\tborder-radius: $u-upload-button-border-radius;\n\t\t\tmargin: $u-upload-botton-margin;\n\t\t\t/* #ifndef APP-NVUE */\n\t\t\tbox-sizing: border-box;\n\t\t\t/* #endif */\n\n\t\t\t&__text {\n\t\t\t\tfont-size: $u-upload-text-font-size;\n\t\t\t\tcolor: $u-upload-text-color;\n\t\t\t\tmargin-top: $u-upload-text-margin-top;\n\t\t\t}\n\n\t\t\t&--hover {\n\t\t\t\tbackground-color: $u-upload-hover-bgColor;\n\t\t\t}\n\n\t\t\t&--disabled {\n\t\t\t\topacity: $u-upload-disabled-opacity;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/u-upload/utils.js",
    "content": "function pickExclude(obj, keys) {\n\t// 某些情况下，type可能会为\n    if (!['[object Object]', '[object File]'].includes(Object.prototype.toString.call(obj))) {\n        return {}\n    }\n    return Object.keys(obj).reduce((prev, key) => {\n        if (!keys.includes(key)) {\n            prev[key] = obj[key]\n        }\n        return prev\n    }, {})\n}\n\nfunction formatImage(res) {\n    return res.tempFiles.map((item) => ({\n        ...pickExclude(item, ['path']),\n        type: 'image',\n        url: item.path,\n        thumb: item.path,\n\t\tsize: item.size,\n\t\t// #ifdef H5\n\t\tname: item.name\n\t\t// #endif\n    }))\n}\n\nfunction formatVideo(res) {\n    return [\n        {\n            ...pickExclude(res, ['tempFilePath', 'thumbTempFilePath', 'errMsg']),\n            type: 'video',\n            url: res.tempFilePath,\n            thumb: res.thumbTempFilePath,\n\t\t\tsize: res.size,\n\t\t\t// #ifdef H5\n\t\t\tname: res.name\n\t\t\t// #endif\n        }\n    ]\n}\n\nfunction formatMedia(res) {\n    return res.tempFiles.map((item) => ({\n        ...pickExclude(item, ['fileType', 'thumbTempFilePath', 'tempFilePath']),\n        type: res.type,\n        url: item.tempFilePath,\n        thumb: res.type === 'video' ? item.thumbTempFilePath : item.tempFilePath,\n\t\tsize: item.size\n    }))\n}\n\nfunction formatFile(res) {\n    return res.tempFiles.map((item) => ({ \n\t\t...pickExclude(item, ['path']), \n\t\turl: item.path, \n\t\tsize:item.size,\n\t\t// #ifdef H5\n\t\tname: item.name,\n\t\ttype: item.type\n\t\t// #endif \n\t}))\n}\nexport function chooseFile({\n    accept,\n    multiple,\n    capture,\n    compressed,\n    maxDuration,\n    sizeType,\n    camera,\n    maxCount\n}) {\n    return new Promise((resolve, reject) => {\n        switch (accept) {\n        case 'image':\n            uni.chooseImage({\n                count: multiple ? Math.min(maxCount, 9) : 1,\n                sourceType: capture,\n                sizeType,\n                success: (res) => resolve(formatImage(res)),\n                fail: reject\n            })\n            break\n            // #ifdef MP-WEIXIN\n            // 只有微信小程序才支持chooseMedia接口\n        case 'media':\n            wx.chooseMedia({\n                count: multiple ? Math.min(maxCount, 9) : 1,\n                sourceType: capture,\n                maxDuration,\n                sizeType,\n                camera,\n                success: (res) => resolve(formatMedia(res)),\n                fail: reject\n            })\n            break\n            // #endif\n        case 'video':\n            uni.chooseVideo({\n                sourceType: capture,\n                compressed,\n                maxDuration,\n                camera,\n                success: (res) => resolve(formatVideo(res)),\n                fail: reject\n            })\n            break\n            // #ifdef MP-WEIXIN || H5\n            // 只有微信小程序才支持chooseMessageFile接口\n        case 'file':\n            // #ifdef MP-WEIXIN\n            wx.chooseMessageFile({\n                count: multiple ? maxCount : 1,\n                type: accept,\n                success: (res) => resolve(formatFile(res)),\n                fail: reject\n            })\n            // #endif\n            // #ifdef H5\n            // 需要hx2.9.9以上才支持uni.chooseFile\n            uni.chooseFile({\n                count: multiple ? maxCount : 1,\n                type: accept,\n                success: (res) => resolve(formatFile(res)),\n                fail: reject\n            })\n            // #endif\n            break\n\t\t\t\t// #endif\n\t\tdefault: \n\t\t\t// 此为保底选项，在accept不为上面任意一项的时候选取全部文件\n\t\t\t// #ifdef MP-WEIXIN\n\t\t\twx.chooseMessageFile({\n\t\t\t    count: multiple ? maxCount : 1,\n\t\t\t    type: 'all',\n\t\t\t    success: (res) => resolve(formatFile(res)),\n\t\t\t    fail: reject\n\t\t\t})\n\t\t\t// #endif\n\t\t\t// #ifdef H5\n\t\t\t// 需要hx2.9.9以上才支持uni.chooseFile\n\t\t\tuni.chooseFile({\n\t\t\t\tcount: multiple ? maxCount : 1,\n\t\t\t\ttype: 'all',\n\t\t\t\tsuccess: (res) => resolve(formatFile(res)),\n\t\t\t\tfail: reject\n\t\t\t})\n\t\t\t// #endif\n        }\n    })\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/components/uview-ui/uview-ui.vue",
    "content": "<template>\n</template>\n\n<template>\n\t<view></view>\n</template>\n\n<script>\n\texport default {\n\t\t\n\t}\n</script>\n\n<style>\n</style>\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/index.js",
    "content": "// 看到此报错，是因为没有配置vue.config.js的【transpileDependencies】，详见：https://www.uviewui.com/components/npmSetting.html#_5-cli模式额外配置\nconst pleaseSetTranspileDependencies = {}, babelTest = pleaseSetTranspileDependencies?.test\n\n\n\n// 引入全局mixin\nimport mixin from './libs/mixin/mixin.js'\n// 小程序特有的mixin\nimport mpMixin from './libs/mixin/mpMixin.js'\n// 全局挂载引入http相关请求拦截插件\nimport Request from './libs/luch-request'\n\n// 路由封装\nimport route from './libs/util/route.js'\n// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制\nimport colorGradient from './libs/function/colorGradient.js'\n\n// 规则检验\nimport test from './libs/function/test.js'\n// 防抖方法\nimport debounce from './libs/function/debounce.js'\n// 节流方法\nimport throttle from './libs/function/throttle.js'\n// 公共文件写入的方法\nimport index from './libs/function/index.js'\n\n// 配置信息\nimport config from './libs/config/config.js'\n// props配置信息\nimport props from './libs/config/props.js'\n// 各个需要fixed的地方的z-index配置文件\nimport zIndex from './libs/config/zIndex.js'\n// 关于颜色的配置，特殊场景使用\nimport color from './libs/config/color.js'\n// 平台\nimport platform from './libs/function/platform'\n\nconst $u = {\n    route,\n    date: index.timeFormat, // 另名date\n    colorGradient: colorGradient.colorGradient,\n    hexToRgb: colorGradient.hexToRgb,\n    rgbToHex: colorGradient.rgbToHex,\n    colorToRgba: colorGradient.colorToRgba,\n    test,\n    type: ['primary', 'success', 'error', 'warning', 'info'],\n    http: new Request(),\n    config, // uView配置信息相关，比如版本号\n    zIndex,\n    debounce,\n    throttle,\n    mixin,\n    mpMixin,\n    props,\n    ...index,\n    color,\n    platform\n}\n\n// $u挂载到uni对象上\nuni.$u = $u\n\nconst install = (Vue) => {\n    // 时间格式化，同时两个名称，date和timeFormat\n    Vue.filter('timeFormat', (timestamp, format) => uni.$u.timeFormat(timestamp, format))\n    Vue.filter('date', (timestamp, format) => uni.$u.timeFormat(timestamp, format))\n    // 将多久以前的方法，注入到全局过滤器\n    Vue.filter('timeFrom', (timestamp, format) => uni.$u.timeFrom(timestamp, format))\n    // 同时挂载到uni和Vue.prototype中\n    // #ifndef APP-NVUE\n    // 只有vue，挂载到Vue.prototype才有意义，因为nvue中全局Vue.prototype和Vue.mixin是无效的\n    Vue.prototype.$u = $u\n    Vue.mixin(mixin)\n    // #endif\n}\n\nexport default {\n    install\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/index.scss",
    "content": "// 引入公共基础类\n@import \"./libs/css/common.scss\";\n@import \"./libs/css/color.scss\";\n\n// 非nvue的样式\n/* #ifndef APP-NVUE */\n@import \"./libs/css/vue.scss\";\n/* #endif */\n\n// nvue的特有样式\n/* #ifdef APP-NVUE */\n@import \"./libs/css/nvue.scss\";\n/* #endif */\n\n// 小程序特有的样式\n/* #ifdef MP */\n@import \"./libs/css/mp.scss\";\n/* #endif */\n\n// H5特有的样式\n/* #ifdef H5 */\n@import \"./libs/css/h5.scss\";\n/* #endif */"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/color.js",
    "content": "// 为了让用户能够自定义主题，会逐步弃用此文件，各颜色通过css提供\n// 为了给某些特殊场景使用和向后兼容，无需删除此文件(2020-06-20)\nconst color = {\n    primary: '#3c9cff',\n    info: '#909399',\n    default: '#909399',\n    warning: '#f9ae3d',\n    error: '#f56c6c',\n    success: '#5ac725',\n    mainColor: '#303133',\n    contentColor: '#606266',\n    tipsColor: '#909399',\n    lightColor: '#c0c4cc',\n    borderColor: '#e4e7ed'\n}\n\nexport default color\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/config.js",
    "content": "// 此版本发布于2022-00-24\nconst version = '2.0.34'\n\n// 开发环境才提示，生产环境不会提示\nif (process.env.NODE_ENV === 'development') {\n\tconsole.log(`\\n %c uView V${version} %c https://uviewui.com/ \\n\\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0; border-radius: 5px;');\n}\n\nexport default {\n    v: version,\n    version,\n    // 主题名称\n    type: [\n        'primary',\n        'success',\n        'info',\n        'error',\n        'warning'\n    ],\n    // 颜色部分，本来可以通过scss的:export导出供js使用，但是奈何nvue不支持\n    color: {\n        'u-primary': '#2979ff',\n        'u-warning': '#ff9900',\n        'u-success': '#19be6b',\n        'u-error': '#fa3534',\n        'u-info': '#909399',\n        'u-main-color': '#303133',\n        'u-content-color': '#606266',\n        'u-tips-color': '#909399',\n        'u-light-color': '#c0c4cc'\n    },\n\t// 默认单位，可以通过配置为rpx，那么在用于传入组件大小参数为数值时，就默认为rpx\n\tunit: 'px'\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/actionSheet.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:44:35\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/actionSheet.js\n */\nexport default {\n    // action-sheet组件\n    actionSheet: {\n        show: false,\n        title: '',\n        description: '',\n        actions: () => [],\n        index: '',\n        cancelText: '',\n        closeOnClickAction: true,\n        safeAreaInsetBottom: true,\n        openType: '',\n        closeOnClickOverlay: true,\n        round: 0\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/album.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:47:24\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/album.js\n */\nexport default {\n    // album 组件\n    album: {\n        urls: () => [],\n        keyName: '',\n        singleSize: 180,\n        multipleSize: 70,\n        space: 6,\n        singleMode: 'scaleToFill',\n        multipleMode: 'aspectFill',\n        maxCount: 9,\n        previewFullImage: true,\n        rowCount: 3,\n        showMore: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/alert.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:48:53\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/alert.js\n */\nexport default {\n    // alert警告组件\n    alert: {\n        title: '',\n        type: 'warning',\n        description: '',\n        closable: false,\n        showIcon: false,\n        effect: 'light',\n        center: false,\n        fontSize: 14\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/avatar.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:49:22\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/avatar.js\n */\nexport default {\n    // avatar 组件\n    avatar: {\n        src: '',\n        shape: 'circle',\n        size: 40,\n        mode: 'scaleToFill',\n        text: '',\n        bgColor: '#c0c4cc',\n        color: '#ffffff',\n        fontSize: 18,\n        icon: '',\n        mpAvatar: false,\n        randomBgColor: false,\n        defaultUrl: '',\n        colorIndex: '',\n        name: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/avatarGroup.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:49:55\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/avatarGroup.js\n */\nexport default {\n    // avatarGroup 组件\n    avatarGroup: {\n        urls: () => [],\n        maxCount: 5,\n        shape: 'circle',\n        mode: 'scaleToFill',\n        showMore: true,\n        size: 40,\n        keyName: '',\n        gap: 0.5,\n\t\textraValue: 0\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/backtop.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:50:18\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/backtop.js\n */\nexport default {\n    // backtop组件\n    backtop: {\n        mode: 'circle',\n        icon: 'arrow-upward',\n        text: '',\n        duration: 100,\n        scrollTop: 0,\n        top: 400,\n        bottom: 100,\n        right: 20,\n        zIndex: 9,\n        iconStyle: () => ({\n            color: '#909399',\n            fontSize: '19px'\n        })\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/badge.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-23 19:51:50\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/badge.js\n */\nexport default {\n    // 徽标数组件\n    badge: {\n        isDot: false,\n        value: '',\n        show: true,\n        max: 999,\n        type: 'error',\n        showZero: false,\n        bgColor: null,\n        color: null,\n        shape: 'circle',\n        numberType: 'overflow',\n        offset: () => [],\n        inverted: false,\n        absolute: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/button.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:51:27\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/button.js\n */\nexport default {\n    // button组件\n    button: {\n        hairline: false,\n        type: 'info',\n        size: 'normal',\n        shape: 'square',\n        plain: false,\n        disabled: false,\n        loading: false,\n        loadingText: '',\n        loadingMode: 'spinner',\n        loadingSize: 15,\n        openType: '',\n        formType: '',\n        appParameter: '',\n        hoverStopPropagation: true,\n        lang: 'en',\n        sessionFrom: '',\n        sendMessageTitle: '',\n        sendMessagePath: '',\n        sendMessageImg: '',\n        showMessageCard: false,\n        dataName: '',\n        throttleTime: 0,\n        hoverStartTime: 0,\n        hoverStayTime: 200,\n        text: '',\n        icon: '',\n        iconColor: '',\n        color: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/calendar.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:52:43\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/calendar.js\n */\nexport default {\n    // calendar 组件\n    calendar: {\n        title: '日期选择',\n        showTitle: true,\n        showSubtitle: true,\n        mode: 'single',\n        startText: '开始',\n        endText: '结束',\n        customList: () => [],\n        color: '#3c9cff',\n        minDate: 0,\n        maxDate: 0,\n        defaultDate: null,\n        maxCount: Number.MAX_SAFE_INTEGER, // Infinity\n        rowHeight: 56,\n        formatter: null,\n        showLunar: false,\n        showMark: true,\n        confirmText: '确定',\n        confirmDisabledText: '确定',\n        show: false,\n        closeOnClickOverlay: false,\n        readonly: false,\n        showConfirm: true,\n        maxRange: Number.MAX_SAFE_INTEGER, // Infinity\n        rangePrompt: '',\n        showRangePrompt: true,\n        allowSameDay: false,\n\t\tround: 0,\n\t\tmonthNum: 3\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/carKeyboard.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:53:20\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/carKeyboard.js\n */\nexport default {\n    // 车牌号键盘\n    carKeyboard: {\n        random: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/cell.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-23 20:53:09\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/cell.js\n */\nexport default {\n\t// cell组件的props\n\tcell: {\n\t\tcustomClass: '',\n\t\ttitle: '',\n\t\tlabel: '',\n\t\tvalue: '',\n\t\ticon: '',\n\t\tdisabled: false,\n\t\tborder: true,\n\t\tcenter: false,\n\t\turl: '',\n\t\tlinkType: 'navigateTo',\n\t\tclickable: false,\n\t\tisLink: false,\n\t\trequired: false,\n\t\tarrowDirection: '',\n\t\ticonStyle: {},\n\t\trightIconStyle: {},\n\t\trightIcon: 'arrow-right',\n\t\ttitleStyle: {},\n\t\tsize: '',\n\t\tstop: true,\n\t\tname: ''\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/cellGroup.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:54:16\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/cellGroup.js\n */\nexport default {\n    // cell-group组件的props\n    cellGroup: {\n        title: '',\n        border: true,\n        customStyle: {}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/checkbox.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-23 21:06:59\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/checkbox.js\n */\nexport default {\n    // checkbox组件\n    checkbox: {\n        name: '',\n        shape: '',\n        size: '',\n        checkbox: false,\n        disabled: '',\n        activeColor: '',\n        inactiveColor: '',\n        iconSize: '',\n        iconColor: '',\n        label: '',\n        labelSize: '',\n        labelColor: '',\n        labelDisabled: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/checkboxGroup.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:54:47\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/checkboxGroup.js\n */\nexport default {\n    // checkbox-group组件\n    checkboxGroup: {\n        name: '',\n        value: () => [],\n        shape: 'square',\n        disabled: false,\n        activeColor: '#2979ff',\n        inactiveColor: '#c8c9cc',\n        size: 18,\n        placement: 'row',\n        labelSize: 14,\n        labelColor: '#303133',\n        labelDisabled: false,\n        iconColor: '#ffffff',\n        iconSize: 12,\n        iconPlacement: 'left',\n        borderBottom: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/circleProgress.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:55:02\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/circleProgress.js\n */\nexport default {\n    // circleProgress 组件\n    circleProgress: {\n        percentage: 30\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/code.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:55:27\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/code.js\n */\n\nexport default {\n    // code 组件\n    code: {\n        seconds: 60,\n        startText: '获取验证码',\n        changeText: 'X秒重新获取',\n        endText: '重新获取',\n        keepRunning: false,\n        uniqueKey: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/codeInput.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:55:58\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/codeInput.js\n */\nexport default {\n    // codeInput 组件\n    codeInput: {\n\t\tadjustPosition: true,\n        maxlength: 6,\n        dot: false,\n        mode: 'box',\n        hairline: false,\n        space: 10,\n        value: '',\n        focus: false,\n        bold: false,\n        color: '#606266',\n        fontSize: 18,\n        size: 35,\n        disabledKeyboard: false,\n        borderColor: '#c9cacc',\n\t\tdisabledDot: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/col.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:56:12\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/col.js\n */\nexport default {\n    // col 组件\n    col: {\n        span: 12,\n        offset: 0,\n        justify: 'start',\n        align: 'stretch',\n        textAlign: 'left'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/collapse.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:56:30\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/collapse.js\n */\nexport default {\n    // collapse 组件\n    collapse: {\n        value: null,\n        accordion: false,\n        border: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/collapseItem.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:56:42\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/collapseItem.js\n */\nexport default {\n    // collapseItem 组件\n    collapseItem: {\n        title: '',\n        value: '',\n        label: '',\n        disabled: false,\n        isLink: true,\n        clickable: true,\n        border: true,\n        align: 'left',\n        name: '',\n        icon: '',\n        duration: 300\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/columnNotice.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:57:16\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/columnNotice.js\n */\nexport default {\n    // columnNotice 组件\n    columnNotice: {\n        text: '',\n        icon: 'volume',\n        mode: '',\n        color: '#f9ae3d',\n        bgColor: '#fdf6ec',\n        fontSize: 14,\n        speed: 80,\n        step: false,\n        duration: 1500,\n        disableTouch: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/countDown.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:11:29\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/countDown.js\n */\nexport default {\n    // u-count-down 计时器组件\n    countDown: {\n        time: 0,\n        format: 'HH:mm:ss',\n        autoStart: true,\n        millisecond: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/countTo.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:57:32\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/countTo.js\n */\nexport default {\n    // countTo 组件\n    countTo: {\n        startVal: 0,\n        endVal: 0,\n        duration: 2000,\n        autoplay: true,\n        decimals: 0,\n        useEasing: true,\n        decimal: '.',\n        color: '#606266',\n        fontSize: 22,\n        bold: false,\n        separator: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/datetimePicker.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:57:48\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/datetimePicker.js\n */\nexport default {\n    // datetimePicker 组件\n    datetimePicker: {\n        show: false,\n        showToolbar: true,\n        value: '',\n        title: '',\n        mode: 'datetime',\n        maxDate: new Date(new Date().getFullYear() + 10, 0, 1).getTime(),\n        minDate: new Date(new Date().getFullYear() - 10, 0, 1).getTime(),\n        minHour: 0,\n        maxHour: 23,\n        minMinute: 0,\n        maxMinute: 59,\n        filter: null,\n        formatter: null,\n        loading: false,\n        itemHeight: 44,\n        cancelText: '取消',\n        confirmText: '确认',\n        cancelColor: '#909193',\n        confirmColor: '#3c9cff',\n        visibleItemCount: 5,\n        closeOnClickOverlay: false,\n        defaultIndex: () => []\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/divider.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:58:03\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/divider.js\n */\nexport default {\n    // divider组件\n    divider: {\n        dashed: false,\n        hairline: true,\n        dot: false,\n        textPosition: 'center',\n        text: '',\n        textSize: 14,\n        textColor: '#909399',\n        lineColor: '#dcdfe6'\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/empty.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:03:27\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/empty.js\n */\nexport default {\n    // empty组件\n    empty: {\n        icon: '',\n        text: '',\n        textColor: '#c0c4cc',\n        textSize: 14,\n        iconColor: '#c0c4cc',\n        iconSize: 90,\n        mode: 'data',\n        width: 160,\n        height: 160,\n        show: true,\n        marginTop: 0\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/form.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:03:49\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/form.js\n */\nexport default {\n    // form 组件\n    form: {\n        model: () => ({}),\n        rules: () => ({}),\n        errorType: 'message',\n        borderBottom: true,\n        labelPosition: 'left',\n        labelWidth: 45,\n        labelAlign: 'left',\n        labelStyle: () => ({})\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/formItem.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:04:32\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/formItem.js\n */\nexport default {\n    // formItem 组件\n    formItem: {\n        label: '',\n        prop: '',\n        borderBottom: '',\n        labelPosition: '',\n        labelWidth: '',\n        rightIcon: '',\n        leftIcon: '',\n        required: false,\n        leftIconStyle: '',\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/gap.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:05:25\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/gap.js\n */\nexport default {\n    // gap组件\n    gap: {\n        bgColor: 'transparent',\n        height: 20,\n        marginTop: 0,\n        marginBottom: 0,\n        customStyle: {}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/grid.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:05:57\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/grid.js\n */\nexport default {\n    // grid组件\n    grid: {\n        col: 3,\n        border: false,\n        align: 'left'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/gridItem.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:06:13\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/gridItem.js\n */\nexport default {\n    // grid-item组件\n    gridItem: {\n        name: null,\n        bgColor: 'transparent'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/icon.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 18:00:14\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/icon.js\n */\nimport config from '../config'\n\nconst {\n    color\n} = config\nexport default {\n    // icon组件\n    icon: {\n        name: '',\n        color: color['u-content-color'],\n        size: '16px',\n        bold: false,\n        index: '',\n        hoverClass: '',\n        customPrefix: 'uicon',\n        label: '',\n        labelPos: 'right',\n        labelSize: '15px',\n        labelColor: color['u-content-color'],\n        space: '3px',\n        imgMode: '',\n        width: '',\n        height: '',\n        top: 0,\n        stop: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/image.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:01:51\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/image.js\n */\nexport default {\n    // image组件\n    image: {\n        src: '',\n        mode: 'aspectFill',\n        width: '300',\n        height: '225',\n        shape: 'square',\n        radius: 0,\n        lazyLoad: true,\n        showMenuByLongpress: true,\n        loadingIcon: 'photo',\n        errorIcon: 'error-circle',\n        showLoading: true,\n        showError: true,\n        fade: true,\n        webp: false,\n        duration: 500,\n        bgColor: '#f3f4f6'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/indexAnchor.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:13:15\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/indexAnchor.js\n */\nexport default {\n    // indexAnchor 组件\n    indexAnchor: {\n        text: '',\n        color: '#606266',\n        size: 14,\n        bgColor: '#dedede',\n        height: 32\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/indexList.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:13:35\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/indexList.js\n */\nexport default {\n    // indexList 组件\n    indexList: {\n        inactiveColor: '#606266',\n        activeColor: '#5677fc',\n        indexList: () => [],\n        sticky: true,\n        customNavHeight: 0\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/input.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:13:55\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/input.js\n */\nexport default {\n\t// index 组件\n\tinput: {\n\t\tvalue: '',\n\t\ttype: 'text',\n\t\tfixed: false,\n\t\tdisabled: false,\n\t\tdisabledColor: '#f5f7fa',\n\t\tclearable: false,\n\t\tpassword: false,\n\t\tmaxlength: -1,\n\t\tplaceholder: null,\n\t\tplaceholderClass: 'input-placeholder',\n\t\tplaceholderStyle: 'color: #c0c4cc',\n\t\tshowWordLimit: false,\n\t\tconfirmType: 'done',\n\t\tconfirmHold: false,\n\t\tholdKeyboard: false,\n\t\tfocus: false,\n\t\tautoBlur: false,\n\t\tdisableDefaultPadding: false,\n\t\tcursor: -1,\n\t\tcursorSpacing: 30,\n\t\tselectionStart: -1,\n\t\tselectionEnd: -1,\n\t\tadjustPosition: true,\n\t\tinputAlign: 'left',\n\t\tfontSize: '15px',\n\t\tcolor: '#303133',\n\t\tprefixIcon: '',\n\t\tprefixIconStyle: '',\n\t\tsuffixIcon: '',\n\t\tsuffixIconStyle: '',\n\t\tborder: 'surround',\n\t\treadonly: false,\n\t\tshape: 'square',\n\t\tformatter: null\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/keyboard.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:07:49\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/keyboard.js\n */\nexport default {\n    // 键盘组件\n    keyboard: {\n        mode: 'number',\n        dotDisabled: false,\n        tooltip: true,\n        showTips: true,\n        tips: '',\n        showCancel: true,\n        showConfirm: true,\n        random: false,\n        safeAreaInsetBottom: true,\n        closeOnClickOverlay: true,\n        show: false,\n        overlay: true,\n        zIndex: 10075,\n        cancelText: '取消',\n        confirmText: '确定',\n        autoChange: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/line.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:04:49\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/line.js\n */\nexport default {\n    // line组件\n    line: {\n        color: '#d6d7d9',\n        length: '100%',\n        direction: 'row',\n        hairline: true,\n        margin: 0,\n        dashed: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/lineProgress.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:14:11\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/lineProgress.js\n */\nexport default {\n    // lineProgress 组件\n    lineProgress: {\n        activeColor: '#19be6b',\n        inactiveColor: '#ececec',\n        percentage: 0,\n        showText: true,\n        height: 12\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/link.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:45:36\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/link.js\n */\nimport config from '../config'\n\nconst {\n    color\n} = config\nexport default {\n    // link超链接组件props参数\n    link: {\n        color: color['u-primary'],\n        fontSize: 15,\n        underLine: false,\n        href: '',\n        mpTips: '链接已复制，请在浏览器打开',\n        lineColor: '',\n        text: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/list.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:14:53\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/list.js\n */\nexport default {\n    // list 组件\n    list: {\n        showScrollbar: false,\n        lowerThreshold: 50,\n        upperThreshold: 0,\n        scrollTop: 0,\n        offsetAccuracy: 10,\n        enableFlex: false,\n        pagingEnabled: false,\n        scrollable: true,\n        scrollIntoView: '',\n        scrollWithAnimation: false,\n        enableBackToTop: false,\n        height: 0,\n        width: 0,\n        preLoadScreen: 1\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/listItem.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:15:40\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/listItem.js\n */\nexport default {\n    // listItem 组件\n    listItem: {\n        anchor: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/loadingIcon.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:45:47\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/loadingIcon.js\n */\nimport config from '../config'\n\nconst {\n    color\n} = config\nexport default {\n    // loading-icon加载中图标组件\n    loadingIcon: {\n        show: true,\n        color: color['u-tips-color'],\n        textColor: color['u-tips-color'],\n        vertical: false,\n        mode: 'spinner',\n        size: 24,\n        textSize: 15,\n        text: '',\n        timingFunction: 'ease-in-out',\n        duration: 1200,\n        inactiveColor: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/loadingPage.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:00:23\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/loadingPage.js\n */\nexport default {\n    // loading-page组件\n    loadingPage: {\n        loadingText: '正在加载',\n        image: '',\n        loadingMode: 'circle',\n        loading: false,\n        bgColor: '#ffffff',\n        color: '#C8C8C8',\n        fontSize: 19,\n        iconSize: 28,\n        loadingColor: '#C8C8C8'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/loadmore.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:15:26\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/loadmore.js\n */\nexport default {\n    // loadmore 组件\n    loadmore: {\n        status: 'loadmore',\n        bgColor: 'transparent',\n        icon: true,\n        fontSize: 14,\n\t\ticonSize: 17,\n        color: '#606266',\n        loadingIcon: 'spinner',\n        loadmoreText: '加载更多',\n        loadingText: '正在加载...',\n        nomoreText: '没有更多了',\n        isDot: false,\n        iconColor: '#b7b7b7',\n        marginTop: 10,\n        marginBottom: 10,\n        height: 'auto',\n        line: false,\n\t\tlineColor: '#E6E8EB',\n\t\tdashed: false,\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/modal.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:15:59\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/modal.js\n */\nexport default {\n    // modal 组件\n    modal: {\n        show: false,\n        title: '',\n        content: '',\n        confirmText: '确认',\n        cancelText: '取消',\n        showConfirmButton: true,\n        showCancelButton: false,\n        confirmColor: '#2979ff',\n        cancelColor: '#606266',\n        buttonReverse: false,\n        zoom: true,\n        asyncClose: false,\n        closeOnClickOverlay: false,\n        negativeTop: 0,\n        width: '650rpx',\n        confirmButtonShape: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/navbar.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:16:18\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/navbar.js\n */\nimport color from '../color'\nexport default {\n    // navbar 组件\n    navbar: {\n        safeAreaInsetTop: true,\n        placeholder: false,\n        fixed: true,\n        border: false,\n        leftIcon: 'arrow-left',\n        leftText: '',\n        rightText: '',\n        rightIcon: '',\n        title: '',\n        bgColor: '#ffffff',\n        titleWidth: '400rpx',\n        height: '44px',\n\t\tleftIconSize: 20,\n\t\tleftIconColor: color.mainColor,\n\t\tautoBack: false,\n\t\ttitleStyle: ''\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/noNetwork.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:16:39\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/noNetwork.js\n */\nexport default {\n    // noNetwork\n    noNetwork: {\n        tips: '哎呀，网络信号丢失',\n        zIndex: '',\n        image: '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'\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/noticeBar.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:17:13\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/noticeBar.js\n */\nexport default {\n    // noticeBar\n    noticeBar: {\n        text: () => [],\n        direction: 'row',\n        step: false,\n        icon: 'volume',\n        mode: '',\n        color: '#f9ae3d',\n        bgColor: '#fdf6ec',\n        speed: 80,\n        fontSize: 14,\n        duration: 2000,\n        disableTouch: true,\n        url: '',\n        linkType: 'navigateTo'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/notify.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:10:21\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/notify.js\n */\nexport default {\n    // notify组件\n    notify: {\n        top: 0,\n        type: 'primary',\n        color: '#ffffff',\n        bgColor: '',\n        message: '',\n        duration: 3000,\n        fontSize: 15,\n        safeAreaInsetTop: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/numberBox.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:11:46\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/numberBox.js\n */\nexport default {\n    // 步进器组件\n    numberBox: {\n        name: '',\n        value: 0,\n        min: 1,\n        max: Number.MAX_SAFE_INTEGER,\n        step: 1,\n        integer: false,\n        disabled: false,\n        disabledInput: false,\n        asyncChange: false,\n        inputWidth: 35,\n        showMinus: true,\n        showPlus: true,\n        decimalLength: null,\n        longPress: true,\n        color: '#323233',\n        buttonSize: 30,\n        bgColor: '#EBECEE',\n        cursorSpacing: 100,\n        disableMinus: false,\n        disablePlus: false,\n        iconStyle: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/numberKeyboard.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:08:05\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/numberKeyboard.js\n */\nexport default {\n    // 数字键盘\n    numberKeyboard: {\n        mode: 'number',\n        dotDisabled: false,\n        random: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/overlay.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:06:50\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/overlay.js\n */\nexport default {\n    // overlay组件\n    overlay: {\n        show: false,\n        zIndex: 10070,\n        duration: 300,\n        opacity: 0.5\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/parse.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:17:33\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/parse.js\n */\nexport default {\n    // parse\n    parse: {\n        copyLink: true,\n        errorImg: '',\n        lazyLoad: false,\n        loadingImg: '',\n        pauseVideo: true,\n        previewImg: true,\n        setTitle: true,\n        showImgMenu: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/picker.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:18:20\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/picker.js\n */\nexport default {\n    // picker\n    picker: {\n        show: false,\n        showToolbar: true,\n        title: '',\n        columns: () => [],\n        loading: false,\n        itemHeight: 44,\n        cancelText: '取消',\n        confirmText: '确定',\n        cancelColor: '#909193',\n        confirmColor: '#3c9cff',\n        visibleItemCount: 5,\n        keyName: 'text',\n        closeOnClickOverlay: false,\n        defaultIndex: () => [],\n\t\timmediateChange: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/popup.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:06:33\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/popup.js\n */\nexport default {\n    // popup组件\n    popup: {\n        show: false,\n        overlay: true,\n        mode: 'bottom',\n        duration: 300,\n        closeable: false,\n        overlayStyle: () => {},\n        closeOnClickOverlay: true,\n        zIndex: 10075,\n        safeAreaInsetBottom: true,\n        safeAreaInsetTop: false,\n        closeIconPos: 'top-right',\n        round: 0,\n        zoom: true,\n        bgColor: '',\n        overlayOpacity: 0.5\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/radio.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:02:34\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/radio.js\n */\nexport default {\n    // radio组件\n    radio: {\n        name: '',\n        shape: '',\n        disabled: '',\n        labelDisabled: '',\n        activeColor: '',\n        inactiveColor: '',\n        iconSize: '',\n        labelSize: '',\n        label: '',\n        labelColor: '',\n        size: '',\n        iconColor: '',\n        placement: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/radioGroup.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:03:12\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/radioGroup.js\n */\nexport default {\n    // radio-group组件\n    radioGroup: {\n        value: '',\n        disabled: false,\n        shape: 'circle',\n        activeColor: '#2979ff',\n        inactiveColor: '#c8c9cc',\n        name: '',\n        size: 18,\n        placement: 'row',\n        label: '',\n        labelColor: '#303133',\n        labelSize: 14,\n        labelDisabled: false,\n        iconColor: '#ffffff',\n        iconSize: 12,\n        borderBottom: false,\n        iconPlacement: 'left'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/rate.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:05:09\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/rate.js\n */\nexport default {\n    // rate组件\n    rate: {\n        value: 1,\n        count: 5,\n        disabled: false,\n        size: 18,\n        inactiveColor: '#b2b2b2',\n        activeColor: '#FA3534',\n        gutter: 4,\n        minCount: 1,\n        allowHalf: false,\n        activeIcon: 'star-fill',\n        inactiveIcon: 'star',\n        touchable: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/readMore.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:18:41\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/readMore.js\n */\nexport default {\n    // readMore\n    readMore: {\n        showHeight: 400,\n        toggle: false,\n        closeText: '展开阅读全文',\n        openText: '收起',\n        color: '#2979ff',\n        fontSize: 14,\n        textIndent: '2em',\n        name: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/row.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:18:58\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/row.js\n */\nexport default {\n    // row\n    row: {\n        gutter: 0,\n        justify: 'start',\n        align: 'center'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/rowNotice.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:19:13\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/rowNotice.js\n */\nexport default {\n    // rowNotice\n    rowNotice: {\n        text: '',\n        icon: 'volume',\n        mode: '',\n        color: '#f9ae3d',\n        bgColor: '#fdf6ec',\n        fontSize: 14,\n        speed: 80\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/scrollList.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:19:28\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/scrollList.js\n */\nexport default {\n    // scrollList\n    scrollList: {\n        indicatorWidth: 50,\n        indicatorBarWidth: 20,\n        indicator: true,\n        indicatorColor: '#f2f2f2',\n        indicatorActiveColor: '#3c9cff',\n        indicatorStyle: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/search.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:19:45\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/search.js\n */\nexport default {\n    // search\n    search: {\n        shape: 'round',\n        bgColor: '#f2f2f2',\n        placeholder: '请输入关键字',\n        clearabled: true,\n        focus: false,\n        showAction: true,\n        actionStyle: () => ({}),\n        actionText: '搜索',\n        inputAlign: 'left',\n        inputStyle: () => ({}),\n        disabled: false,\n        borderColor: 'transparent',\n        searchIconColor: '#909399',\n        searchIconSize: 22,\n        color: '#606266',\n        placeholderColor: '#909399',\n        searchIcon: 'search',\n        margin: '0',\n        animation: false,\n        value: '',\n        maxlength: '-1',\n        height: 32,\n        label: null\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/section.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:07:33\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/section.js\n */\nexport default {\n    // u-section组件\n    section: {\n        title: '',\n        subTitle: '更多',\n        right: true,\n        fontSize: 15,\n        bold: true,\n        color: '#303133',\n        subColor: '#909399',\n        showLine: true,\n        lineColor: '',\n        arrow: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/skeleton.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:20:14\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/skeleton.js\n */\nexport default {\n    // skeleton\n    skeleton: {\n        loading: true,\n        animate: true,\n        rows: 0,\n        rowsWidth: '100%',\n        rowsHeight: 18,\n        title: true,\n        titleWidth: '50%',\n        titleHeight: 18,\n        avatar: false,\n        avatarSize: 32,\n        avatarShape: 'circle'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/slider.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:08:25\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/slider.js\n */\nexport default {\n    // slider组件\n    slider: {\n        value: 0,\n        blockSize: 18,\n        min: 0,\n        max: 100,\n        step: 1,\n        activeColor: '#2979ff',\n        inactiveColor: '#c0c4cc',\n        blockColor: '#ffffff',\n        showValue: false,\n\t\tdisabled:false,\n        blockStyle: () => {}\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/statusBar.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:20:39\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/statusBar.js\n */\nexport default {\n    // statusBar\n    statusBar: {\n        bgColor: 'transparent'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/steps.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:12:37\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/steps.js\n */\nexport default {\n    // steps组件\n    steps: {\n        direction: 'row',\n        current: 0,\n        activeColor: '#3c9cff',\n        inactiveColor: '#969799',\n        activeIcon: '',\n        inactiveIcon: '',\n        dot: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/stepsItem.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:12:55\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/stepsItem.js\n */\nexport default {\n    // steps-item组件\n    stepsItem: {\n        title: '',\n        desc: '',\n        iconSize: 17,\n        error: false\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/sticky.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:01:30\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/sticky.js\n */\nexport default {\n    // sticky组件\n    sticky: {\n        offsetTop: 0,\n        customNavHeight: 0,\n        disabled: false,\n        bgColor: 'transparent',\n        zIndex: '',\n        index: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/subsection.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:12:20\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/subsection.js\n */\nexport default {\n    // subsection组件\n    subsection: {\n        list: [],\n        current: 0,\n        activeColor: '#3c9cff',\n        inactiveColor: '#303133',\n        mode: 'button',\n        fontSize: 12,\n        bold: true,\n        bgColor: '#eeeeef',\n\t\tkeyName: 'name'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/swipeAction.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:00:42\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/swipeAction.js\n */\nexport default {\n    // swipe-action组件\n    swipeAction: {\n        autoClose: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/swipeActionItem.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:01:13\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/swipeActionItem.js\n */\nexport default {\n    // swipeActionItem 组件\n    swipeActionItem: {\n        show: false,\n        name: '',\n        disabled: false,\n        threshold: 20,\n        autoClose: true,\n        options: [],\n        duration: 300\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/swiper.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:21:38\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/swiper.js\n */\nexport default {\n    // swiper 组件\n    swiper: {\n        list: () => [],\n        indicator: false,\n        indicatorActiveColor: '#FFFFFF',\n        indicatorInactiveColor: 'rgba(255, 255, 255, 0.35)',\n        indicatorStyle: '',\n        indicatorMode: 'line',\n        autoplay: true,\n        current: 0,\n        currentItemId: '',\n        interval: 3000,\n        duration: 300,\n        circular: false,\n        previousMargin: 0,\n        nextMargin: 0,\n        acceleration: false,\n        displayMultipleItems: 1,\n        easingFunction: 'default',\n        keyName: 'url',\n        imgMode: 'aspectFill',\n        height: 130,\n        bgColor: '#f3f4f6',\n        radius: 4,\n        loading: false,\n        showTitle: false\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/swipterIndicator.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:22:07\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/swiperIndicator.js\n */\nexport default {\n    // swiperIndicator 组件\n    swiperIndicator: {\n        length: 0,\n        current: 0,\n        indicatorActiveColor: '',\n        indicatorInactiveColor: '',\n\t\tindicatorMode: 'line'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/switch.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:22:24\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/switch.js\n */\nexport default {\n    // switch\n    switch: {\n        loading: false,\n        disabled: false,\n        size: 25,\n        activeColor: '#2979ff',\n        inactiveColor: '#ffffff',\n        value: false,\n        activeValue: true,\n        inactiveValue: false,\n        asyncChange: false,\n        space: 0\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/tabbar.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:22:40\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/tabbar.js\n */\nexport default {\n    // tabbar\n    tabbar: {\n        value: null,\n        safeAreaInsetBottom: true,\n        border: true,\n        zIndex: 1,\n        activeColor: '#1989fa',\n        inactiveColor: '#7d7e80',\n        fixed: true,\n        placeholder: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/tabbarItem.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:22:55\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/tabbarItem.js\n */\nexport default {\n    //\n    tabbarItem: {\n        name: null,\n        icon: '',\n        badge: null,\n        dot: false,\n        text: '',\n        badgeStyle: 'top: 6px;right:2px;'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/tabs.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:23:14\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/tabs.js\n */\nexport default {\n    //\n    tabs: {\n        duration: 300,\n        list: () => [],\n        lineColor: '#3c9cff',\n        activeStyle: () => ({\n            color: '#303133'\n        }),\n        inactiveStyle: () => ({\n            color: '#606266'\n        }),\n        lineWidth: 20,\n        lineHeight: 3,\n        lineBgSize: 'cover',\n        itemStyle: () => ({\n            height: '44px'\n        }),\n        scrollable: true,\n\t\tcurrent: 0,\n\t\tkeyName: 'name'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/tag.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:23:37\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/tag.js\n */\nexport default {\n    // tag 组件\n    tag: {\n        type: 'primary',\n        disabled: false,\n        size: 'medium',\n        shape: 'square',\n        text: '',\n        bgColor: '',\n        color: '',\n        borderColor: '',\n        closeColor: '#C6C7CB',\n        name: '',\n        plainFill: false,\n        plain: false,\n        closable: false,\n        show: true,\n        icon: ''\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/text.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:23:58\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/text.js\n */\nexport default {\n    // text 组件\n    text: {\n        type: '',\n        show: true,\n        text: '',\n        prefixIcon: '',\n        suffixIcon: '',\n        mode: '',\n        href: '',\n        format: '',\n        call: false,\n        openType: '',\n        bold: false,\n        block: false,\n        lines: '',\n        color: '#303133',\n        size: 15,\n        iconStyle: () => ({\n            fontSize: '15px'\n        }),\n        decoration: 'none',\n        margin: 0,\n        lineHeight: '',\n        align: 'left',\n        wordWrap: 'normal'\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/textarea.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:24:32\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/textarea.js\n */\nexport default {\n\t// textarea 组件\n\ttextarea: {\n\t\tvalue: '',\n\t\tplaceholder: '',\n\t\tplaceholderClass: 'textarea-placeholder',\n\t\tplaceholderStyle: 'color: #c0c4cc',\n\t\theight: 70,\n\t\tconfirmType: 'done',\n\t\tdisabled: false,\n\t\tcount: false,\n\t\tfocus: false,\n\t\tautoHeight: false,\n\t\tfixed: false,\n\t\tcursorSpacing: 0,\n\t\tcursor: '',\n\t\tshowConfirmBar: true,\n\t\tselectionStart: -1,\n\t\tselectionEnd: -1,\n\t\tadjustPosition: true,\n\t\tdisableDefaultPadding: false,\n\t\tholdKeyboard: false,\n\t\tmaxlength: 140,\n\t\tborder: 'surround',\n\t\tformatter: null\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/toast.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:07:07\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/toast.js\n */\nexport default {\n    // toast组件\n    toast: {\n        zIndex: 10090,\n        loading: false,\n        text: '',\n        icon: '',\n        type: '',\n        loadingMode: '',\n        show: '',\n        overlay: false,\n        position: 'center',\n        params: () => {},\n        duration: 2000,\n        isTab: false,\n        url: '',\n        callback: null,\n        back: false\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/toolbar.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:24:55\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/toolbar.js\n */\nexport default {\n    // toolbar 组件\n    toolbar: {\n        show: true,\n        cancelText: '取消',\n        confirmText: '确认',\n        cancelColor: '#909193',\n        confirmColor: '#3c9cff',\n        title: ''\n    }\n\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/tooltip.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:25:14\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/tooltip.js\n */\nexport default {\n    // tooltip 组件\n    tooltip: {\n        text: '',\n        copyText: '',\n        size: 14,\n        color: '#606266',\n        bgColor: 'transparent',\n        direction: 'top',\n        zIndex: 10071,\n        showCopy: true,\n        buttons: () => [],\n        overlay: true,\n        showToast: true\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/transition.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 16:59:00\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/transition.js\n */\nexport default {\n    // transition动画组件的props\n    transition: {\n        show: false,\n        mode: 'fade',\n        duration: '300',\n        timingFunction: 'ease-out'\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props/upload.js",
    "content": "/*\n * @Author       : LQ\n * @Description  :\n * @version      : 1.0\n * @Date         : 2021-08-20 16:44:21\n * @LastAuthor   : LQ\n * @lastTime     : 2021-08-20 17:09:50\n * @FilePath     : /u-view2.0/uview-ui/libs/config/props/upload.js\n */\nexport default {\n\t// upload组件\n\tupload: {\n\t\taccept: 'image',\n\t\tcapture: () => ['album', 'camera'],\n\t\tcompressed: true,\n\t\tcamera: 'back',\n\t\tmaxDuration: 60,\n\t\tuploadIcon: 'camera-fill',\n\t\tuploadIconColor: '#D3D4D6',\n\t\tuseBeforeRead: false,\n\t\tpreviewFullImage: true,\n\t\tmaxCount: 52,\n\t\tdisabled: false,\n\t\timageMode: 'aspectFill',\n\t\tname: '',\n\t\tsizeType: () => ['original', 'compressed'],\n\t\tmultiple: false,\n\t\tdeletable: true,\n\t\tmaxSize: Number.MAX_VALUE,\n\t\tfileList: () => [],\n\t\tuploadText: '',\n\t\twidth: 80,\n\t\theight: 80,\n\t\tpreviewImage: true\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/props.js",
    "content": "/**\n * 此文件的作用为统一配置所有组件的props参数\n * 借此用户可以全局覆盖组件的props默认值\n * 无需在每个引入组件的页面中都配置一次\n */\nimport config from './config'\n\nimport actionSheet from './props/actionSheet.js'\nimport album from './props/album.js'\nimport alert from './props/alert.js'\nimport avatar from './props/avatar'\nimport avatarGroup from './props/avatarGroup'\nimport backtop from './props/backtop'\nimport badge from './props/badge'\nimport button from './props/button'\nimport calendar from './props/calendar'\nimport carKeyboard from './props/carKeyboard'\nimport cell from './props/cell'\nimport cellGroup from './props/cellGroup'\nimport checkbox from './props/checkbox'\nimport checkboxGroup from './props/checkboxGroup'\nimport circleProgress from './props/circleProgress'\nimport code from './props/code'\nimport codeInput from './props/codeInput'\nimport col from './props/col'\nimport collapse from './props/collapse'\nimport collapseItem from './props/collapseItem'\nimport columnNotice from './props/columnNotice'\nimport countDown from './props/countDown'\nimport countTo from './props/countTo'\nimport datetimePicker from './props/datetimePicker'\nimport divider from './props/divider'\nimport empty from './props/empty'\nimport form from './props/form'\nimport formItem from './props/formItem'\nimport gap from './props/gap'\nimport grid from './props/grid'\nimport gridItem from './props/gridItem'\nimport icon from './props/icon'\nimport image from './props/image'\nimport indexAnchor from './props/indexAnchor'\nimport indexList from './props/indexList'\nimport input from './props/input'\nimport keyboard from './props/keyboard'\nimport line from './props/line'\nimport lineProgress from './props/lineProgress'\nimport link from './props/link'\nimport list from './props/list'\nimport listItem from './props/listItem'\nimport loadingIcon from './props/loadingIcon'\nimport loadingPage from './props/loadingPage'\nimport loadmore from './props/loadmore'\nimport modal from './props/modal'\nimport navbar from './props/navbar'\nimport noNetwork from './props/noNetwork'\nimport noticeBar from './props/noticeBar'\nimport notify from './props/notify'\nimport numberBox from './props/numberBox'\nimport numberKeyboard from './props/numberKeyboard'\nimport overlay from './props/overlay'\nimport parse from './props/parse'\nimport picker from './props/picker'\nimport popup from './props/popup'\nimport radio from './props/radio'\nimport radioGroup from './props/radioGroup'\nimport rate from './props/rate'\nimport readMore from './props/readMore'\nimport row from './props/row'\nimport rowNotice from './props/rowNotice'\nimport scrollList from './props/scrollList'\nimport search from './props/search'\nimport section from './props/section'\nimport skeleton from './props/skeleton'\nimport slider from './props/slider'\nimport statusBar from './props/statusBar'\nimport steps from './props/steps'\nimport stepsItem from './props/stepsItem'\nimport sticky from './props/sticky'\nimport subsection from './props/subsection'\nimport swipeAction from './props/swipeAction'\nimport swipeActionItem from './props/swipeActionItem'\nimport swiper from './props/swiper'\nimport swipterIndicator from './props/swipterIndicator'\nimport _switch from './props/switch'\nimport tabbar from './props/tabbar'\nimport tabbarItem from './props/tabbarItem'\nimport tabs from './props/tabs'\nimport tag from './props/tag'\nimport text from './props/text'\nimport textarea from './props/textarea'\nimport toast from './props/toast'\nimport toolbar from './props/toolbar'\nimport tooltip from './props/tooltip'\nimport transition from './props/transition'\nimport upload from './props/upload'\n\nconst {\n    color\n} = config\n\nexport default {\n    ...actionSheet,\n    ...album,\n    ...alert,\n    ...avatar,\n    ...avatarGroup,\n    ...backtop,\n    ...badge,\n    ...button,\n    ...calendar,\n    ...carKeyboard,\n    ...cell,\n    ...cellGroup,\n    ...checkbox,\n    ...checkboxGroup,\n    ...circleProgress,\n    ...code,\n    ...codeInput,\n    ...col,\n    ...collapse,\n    ...collapseItem,\n    ...columnNotice,\n    ...countDown,\n    ...countTo,\n    ...datetimePicker,\n    ...divider,\n    ...empty,\n    ...form,\n    ...formItem,\n    ...gap,\n    ...grid,\n    ...gridItem,\n    ...icon,\n    ...image,\n    ...indexAnchor,\n    ...indexList,\n    ...input,\n    ...keyboard,\n    ...line,\n    ...lineProgress,\n    ...link,\n    ...list,\n    ...listItem,\n    ...loadingIcon,\n    ...loadingPage,\n    ...loadmore,\n    ...modal,\n    ...navbar,\n    ...noNetwork,\n    ...noticeBar,\n    ...notify,\n    ...numberBox,\n    ...numberKeyboard,\n    ...overlay,\n    ...parse,\n    ...picker,\n    ...popup,\n    ...radio,\n    ...radioGroup,\n    ...rate,\n    ...readMore,\n    ...row,\n    ...rowNotice,\n    ...scrollList,\n    ...search,\n    ...section,\n    ...skeleton,\n    ...slider,\n    ...statusBar,\n    ...steps,\n    ...stepsItem,\n    ...sticky,\n    ...subsection,\n    ...swipeAction,\n    ...swipeActionItem,\n    ...swiper,\n    ...swipterIndicator,\n    ..._switch,\n    ...tabbar,\n    ...tabbarItem,\n    ...tabs,\n    ...tag,\n    ...text,\n    ...textarea,\n    ...toast,\n    ...toolbar,\n    ...tooltip,\n    ...transition,\n    ...upload\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/config/zIndex.js",
    "content": "// uniapp在H5中各API的z-index值如下：\n/**\n * actionsheet: 999\n * modal: 999\n * navigate: 998\n * tabbar: 998\n * toast: 999\n */\n\nexport default {\n    toast: 10090,\n    noNetwork: 10080,\n    // popup包含popup，actionsheet，keyboard，picker的值\n    popup: 10075,\n    mask: 10070,\n    navbar: 980,\n    topTips: 975,\n    sticky: 970,\n    indexListSticky: 965\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/color.scss",
    "content": ".u-primary-light {\n\tcolor: $u-primary-light;\n}\n\n.u-warning-light {\n\tcolor: $u-warning-light;\n}\n\n.u-success-light {\n\tcolor: $u-success-light;\n}\n\n.u-error-light {\n\tcolor: $u-error-light;\n}\n\n.u-info-light {\n\tcolor: $u-info-light;\n}\n\n.u-primary-light-bg {\n\tbackground-color: $u-primary-light;\n}\n\n.u-warning-light-bg {\n\tbackground-color: $u-warning-light;\n}\n\n.u-success-light-bg {\n\tbackground-color: $u-success-light;\n}\n\n.u-error-light-bg {\n\tbackground-color: $u-error-light;\n}\n\n.u-info-light-bg {\n\tbackground-color: $u-info-light;\n}\n\n.u-primary-dark {\n\tcolor: $u-primary-dark;\n}\n\n.u-warning-dark {\n\tcolor: $u-warning-dark;\n}\n\n.u-success-dark {\n\tcolor: $u-success-dark;\n}\n\n.u-error-dark {\n\tcolor: $u-error-dark;\n}\n\n.u-info-dark {\n\tcolor: $u-info-dark;\n}\n\n.u-primary-dark-bg {\n\tbackground-color: $u-primary-dark;\n}\n\n.u-warning-dark-bg {\n\tbackground-color: $u-warning-dark;\n}\n\n.u-success-dark-bg {\n\tbackground-color: $u-success-dark;\n}\n\n.u-error-dark-bg {\n\tbackground-color: $u-error-dark;\n}\n\n.u-info-dark-bg {\n\tbackground-color: $u-info-dark;\n}\n\n.u-primary-disabled {\n\tcolor: $u-primary-disabled;\n}\n\n.u-warning-disabled {\n\tcolor: $u-warning-disabled;\n}\n\n.u-success-disabled {\n\tcolor: $u-success-disabled;\n}\n\n.u-error-disabled {\n\tcolor: $u-error-disabled;\n}\n\n.u-info-disabled {\n\tcolor: $u-info-disabled;\n}\n\n.u-primary {\n\tcolor: $u-primary;\n}\n\n.u-warning {\n\tcolor: $u-warning;\n}\n\n.u-success {\n\tcolor: $u-success;\n}\n\n.u-error {\n\tcolor: $u-error;\n}\n\n.u-info {\n\tcolor: $u-info;\n}\n\n.u-primary-bg {\n\tbackground-color: $u-primary;\n}\n\n.u-warning-bg {\n\tbackground-color: $u-warning;\n}\n\n.u-success-bg {\n\tbackground-color: $u-success;\n}\n\n.u-error-bg {\n\tbackground-color: $u-error;\n}\n\n.u-info-bg {\n\tbackground-color: $u-info;\n}\n\n.u-main-color {\n\tcolor: $u-main-color;\n}\n\n.u-content-color {\n\tcolor: $u-content-color;\n}\n\n.u-tips-color {\n\tcolor: $u-tips-color;\n}\n\n.u-light-color {\n\tcolor: $u-light-color;\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/common.scss",
    "content": "// 超出行数，自动显示行尾省略号，最多5行\n// 来自uView的温馨提示：当您在控制台看到此报错，说明需要在App.vue的style标签加上【lang=\"scss\"】\n@for $i from 1 through 5 {\n\t.u-line-#{$i} {\n\t\t/* #ifdef APP-NVUE */\n\t\t// nvue下，可以直接使用lines属性，这是weex特有样式\n\t\tlines: $i;\n\t\ttext-overflow: ellipsis;\n\t\toverflow: hidden;\n\t\tflex: 1;\n\t\t/* #endif */\n\n\t\t/* #ifndef APP-NVUE */\n\t\t// vue下，单行和多行显示省略号需要单独处理\n\t\t@if $i == '1' {\n\t\t\toverflow: hidden;\n\t\t\twhite-space: nowrap;\n\t\t\ttext-overflow: ellipsis;\n\t\t} @else {\n\t\t\tdisplay: -webkit-box!important;\n\t\t\toverflow: hidden;\n\t\t\ttext-overflow: ellipsis;\n\t\t\tword-break: break-all;\n\t\t\t-webkit-line-clamp: $i;\n\t\t\t-webkit-box-orient: vertical!important;\n\t\t}\n\t\t/* #endif */\n\t}\n}\n\n\n// 此处加上!important并非随意乱用，而是因为目前*.nvue页面编译到H5时，\n// App.vue的样式会被uni-app的view元素的自带border属性覆盖，导致无效\n// 综上，这是uni-app的缺陷导致我们为了多端兼容，而必须要加上!important\n// 移动端兼容性较好，直接使用0.5px去实现细边框，不使用伪元素形式实现\n.u-border {\n\tborder-width: 0.5px!important;\n\tborder-color: $u-border-color!important;\n    border-style: solid;\n}\n\n.u-border-top {\n\tborder-top-width: 0.5px!important;\n\tborder-color: $u-border-color!important;\n    border-top-style: solid;\n}\n\n.u-border-left {\n\tborder-left-width: 0.5px!important;\n\tborder-color: $u-border-color!important;\n    border-left-style: solid;\n}\n\n.u-border-right {\n\tborder-right-width: 0.5px!important;\n\tborder-color: $u-border-color!important;\n    border-right-style: solid;\n}\n\n.u-border-bottom {\n\tborder-bottom-width: 0.5px!important;\n\tborder-color: $u-border-color!important;\n    border-bottom-style: solid;\n}\n\n.u-border-top-bottom {\n\tborder-top-width: 0.5px!important;\n\tborder-bottom-width: 0.5px!important;\n\tborder-color: $u-border-color!important;\n    border-top-style: solid;\n    border-bottom-style: solid;\n}\n\n// 去除button的所有默认样式，让其表现跟普通的view、text元素一样\n.u-reset-button {\n\tpadding: 0;\n\tbackground-color: transparent;\n\t/* #ifndef APP-PLUS */\n\tfont-size: inherit;\n\tline-height: inherit;\n\tcolor: inherit;\n\t/* #endif */\n\t/* #ifdef APP-NVUE */\n\tborder-width: 0;\n\t/* #endif */\n}\n\n/* #ifndef APP-NVUE */\n.u-reset-button::after {\n   border: none;\n}\n/* #endif */\n\n.u-hover-class {\n\topacity: 0.7;\n}\n\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/components.scss",
    "content": "@import \"./mixin.scss\";\n\n/* #ifndef APP-NVUE */\n// 由于uView是基于nvue环境进行开发的，此环境中普通元素默认为flex-direction: column;\n// 所以在非nvue中，需要对元素进行重置为flex-direction: column; 否则可能会表现异常\nview, scroll-view, swiper-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tflex-shrink: 0;\n\tflex-grow: 0;\n\tflex-basis: auto;\n\talign-items: stretch;\n\talign-content: flex-start;\n}\n/* #endif */\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/flex.scss",
    "content": "// .u-flex {\n// \t@include vue-flex(row);\n// }\n\n// .u-flex-x {\n// \t@include vue-flex(row);\n// }\n\n// .u-flex-y {\n// \t@include vue-flex(column);\n// }\n\n// .u-flex-xy-center {\n// \t@include vue-flex(row);\n// \tjustify-content: center;\n// \talign-items: center;\n// }\n\n// .u-flex-x-center {\n// \t@include vue-flex(row);\n// \tjustify-content: center;\n// }\n\n// .u-flex-y-center {\n// \t@include vue-flex(column);\n// \tjustify-content: center;\n// }\n\n\n// flex布局\n.u-flex,\n.u-flex-row,\n.u-flex-x {\n\t@include flex;\n}\n\n.u-flex-y,\n.u-flex-column {\n\t@include flex(column);\n}\n\n.u-flex-x-center {\n\t@include flex;\n\tjustify-content: center;\n}\n\n.u-flex-xy-center {\n\t@include flex;\n\tjustify-content: center;\n\talign-items: center;\n}\n\n.u-flex-y-center {\n\t@include flex;\n\talign-items: center;\n}\n\n.u-flex-x-left {\n\t@include flex;\n}\n\n.u-flex-x-reverse,\n.u-flex-row-reverse {\n\tflex-direction: row-reverse;\n}\n\n.u-flex-y-reverse,\n.u-flex-column-reverse {\n\tflex-direction: column-reverse;\n}\n\n/* #ifndef APP-NVUE */\n// 此处为vue版本的简写，因为nvue不支持同时作用于两个类名的样式写法\n// nvue下只能写成class=\"u-flex-x u-flex-x-reverse的形式\"\n.u-flex.u-flex-reverse,\n.u-flex-row.u-flex-reverse,\n.u-flex-x.u-flex-reverse {\n\tflex-direction: row-reverse;\n}\n\n.u-flex-column.u-flex-reverse,\n.u-flex-y.u-flex-reverse {\n\tflex-direction: column-reverse;\n}\n\n// 自动伸缩\n.u-flex-fill {\n\tflex: 1 1 auto\n}\n\n// 边界自动伸缩\n.u-margin-top-auto,\n.u-m-t-auto {\n\tmargin-top: auto !important\n}\n\n.u-margin-right-auto,\n.u-m-r-auto {\n\tmargin-right: auto !important\n}\n\n.u-margin-bottom-auto,\n.u-m-b-auto {\n\tmargin-bottom: auto !important\n}\n\n.u-margin-left-auto,\n.u-m-l-auto {\n\tmargin-left: auto !important\n}\n\n.u-margin-center-auto,\n.u-m-c-auto {\n\tmargin-left: auto !important;\n\tmargin-right: auto !important\n}\n\n.u-margin-middle-auto,\n.u-m-m-auto {\n\tmargin-top: auto !important;\n\tmargin-bottom: auto !important\n}\n/* #endif */\n\n// 换行\n.u-flex-wrap {\n\tflex-wrap: wrap;\n}\n\n// 反向换行\n.u-flex-wrap-reverse {\n\tflex-wrap: wrap-reverse;\n}\n\n// 主轴起点对齐\n.u-flex-start {\n\tjustify-content: flex-start\n}\n\n// 主轴中间对齐\n.u-flex-center {\n\tjustify-content: center\n}\n\n// 主轴终点对齐\n.u-flex-end {\n\tjustify-content: flex-end\n}\n\n// 主轴等比间距\n.u-flex-between {\n\tjustify-content: space-between\n}\n\n// 主轴均分间距\n.u-flex-around {\n\tjustify-content: space-around\n}\n\n// 交叉轴起点对齐\n.u-flex-items-start {\n\talign-items: flex-start\n}\n\n// 交叉轴中间对齐\n.u-flex-items-center {\n\talign-items: center\n}\n\n// 交叉轴终点对齐\n.u-flex-items-end {\n\talign-items: flex-end\n}\n\n// 交叉轴第一行文字基线对齐\n.u-flex-items-baseline {\n\talign-items: baseline\n}\n\n// 交叉轴方向拉伸对齐\n.u-flex-items-stretch {\n\talign-items: stretch\n}\n\n\n// 以下属于项目(子元素)的类\n\n// 子元素交叉轴起点对齐\n.u-flex-self-start {\n\talign-self: flex-start\n}\n\n// 子元素交叉轴居中对齐\n.u-flex-self-center {\n\talign-self: center\n}\n\n// 子元素交叉轴终点对齐\n.u-flex-self-end {\n\talign-self: flex-end\n}\n\n// 子元素交叉轴第一行文字基线对齐\n.u-flex-self-baseline {\n\talign-self: baseline\n}\n\n// 子元素交叉轴方向拉伸对齐\n.u-flex-self-stretch {\n\talign-self: stretch\n}\n\n// 多轴交叉时的对齐方式\n\n// 起点对齐\n.u-flex-content-start {\n\talign-content: flex-start\n}\n\n// 居中对齐\n.u-flex-content-center {\n\talign-content: center\n}\n\n// 终点对齐\n.u-flex-content-end {\n\talign-content: flex-end\n}\n\n// 两端对齐\n.u-flex-content-between {\n\talign-content: space-between\n}\n\n// 均分间距\n.u-flex-content-around {\n\talign-content: space-around\n}\n\n// 全部居中对齐\n.u-flex-middle {\n\tjustify-content: center;\n\talign-items: center;\n\talign-self: center;\n\talign-content: center\n}\n\n// 是否可以放大\n.u-flex-grow {\n\tflex-grow: 1\n}\n\n// 是否可以缩小\n.u-flex-shrink {\n\tflex-shrink: 1\n}\n\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/h5.scss",
    "content": ""
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/mixin.scss",
    "content": "// 通过scss的mixin功能，把原来需要写4行的css，变成一行\n// 目的是保持代码干净整洁，不至于在nvue下，到处都要写display:flex的条件编译\n@mixin flex($direction: row) {\n\t/* #ifndef APP-NVUE */\n\tdisplay: flex;\n\t/* #endif */\n\tflex-direction: $direction;\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/mp.scss",
    "content": ""
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/nvue.scss",
    "content": ""
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/css/vue.scss",
    "content": "// 历遍生成4个方向的底部安全区\n@each $d in top, right, bottom, left {\n\t.u-safe-area-inset-#{$d} {\n\t\tpadding-#{$d}: 0;\n\t\tpadding-#{$d}: constant(safe-area-inset-#{$d});  \n\t\tpadding-#{$d}: env(safe-area-inset-#{$d});  \n\t}\n}\n\n//提升H5端uni.toast()的层级，避免被uView的modal等遮盖\n/* #ifdef H5 */\nuni-toast {\n    z-index: 10090;\n}\nuni-toast .uni-toast {\n   z-index: 10090;\n}\n/* #endif */\n\n// 隐藏scroll-view的滚动条\n::-webkit-scrollbar {\n    display: none;  \n    width: 0 !important;  \n    height: 0 !important;  \n    -webkit-appearance: none;  \n    background: transparent;  \n}"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/function/colorGradient.js",
    "content": "/**\n * 求两个颜色之间的渐变值\n * @param {string} startColor 开始的颜色\n * @param {string} endColor 结束的颜色\n * @param {number} step 颜色等分的份额\n * */\nfunction colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) {\n    const startRGB = hexToRgb(startColor, false) // 转换为rgb数组模式\n    const startR = startRGB[0]\n    const startG = startRGB[1]\n    const startB = startRGB[2]\n\n    const endRGB = hexToRgb(endColor, false)\n    const endR = endRGB[0]\n    const endG = endRGB[1]\n    const endB = endRGB[2]\n\n    const sR = (endR - startR) / step // 总差值\n    const sG = (endG - startG) / step\n    const sB = (endB - startB) / step\n    const colorArr = []\n    for (let i = 0; i < step; i++) {\n        // 计算每一步的hex值\n        let hex = rgbToHex(`rgb(${Math.round((sR * i + startR))},${Math.round((sG * i + startG))},${Math.round((sB\n\t\t\t* i + startB))})`)\n        // 确保第一个颜色值为startColor的值\n        if (i === 0) hex = rgbToHex(startColor)\n        // 确保最后一个颜色值为endColor的值\n        if (i === step - 1) hex = rgbToHex(endColor)\n        colorArr.push(hex)\n    }\n    return colorArr\n}\n\n// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)\nfunction hexToRgb(sColor, str = true) {\n    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/\n    sColor = String(sColor).toLowerCase()\n    if (sColor && reg.test(sColor)) {\n        if (sColor.length === 4) {\n            let sColorNew = '#'\n            for (let i = 1; i < 4; i += 1) {\n                sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))\n            }\n            sColor = sColorNew\n        }\n        // 处理六位的颜色值\n        const sColorChange = []\n        for (let i = 1; i < 7; i += 2) {\n            sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))\n        }\n        if (!str) {\n            return sColorChange\n        }\n        return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})`\n    } if (/^(rgb|RGB)/.test(sColor)) {\n        const arr = sColor.replace(/(?:\\(|\\)|rgb|RGB)*/g, '').split(',')\n        return arr.map((val) => Number(val))\n    }\n    return sColor\n}\n\n// 将rgb表示方式转换为hex表示方式\nfunction rgbToHex(rgb) {\n    const _this = rgb\n    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/\n    if (/^(rgb|RGB)/.test(_this)) {\n        const aColor = _this.replace(/(?:\\(|\\)|rgb|RGB)*/g, '').split(',')\n        let strHex = '#'\n        for (let i = 0; i < aColor.length; i++) {\n            let hex = Number(aColor[i]).toString(16)\n            hex = String(hex).length == 1 ? `${0}${hex}` : hex // 保证每个rgb的值为2位\n            if (hex === '0') {\n                hex += hex\n            }\n            strHex += hex\n        }\n        if (strHex.length !== 7) {\n            strHex = _this\n        }\n        return strHex\n    } if (reg.test(_this)) {\n        const aNum = _this.replace(/#/, '').split('')\n        if (aNum.length === 6) {\n            return _this\n        } if (aNum.length === 3) {\n            let numHex = '#'\n            for (let i = 0; i < aNum.length; i += 1) {\n                numHex += (aNum[i] + aNum[i])\n            }\n            return numHex\n        }\n    } else {\n        return _this\n    }\n}\n\n/**\n* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba（255，255，255，0.5）字符串\n* sHex为传入的十六进制的色值\n* alpha为rgba的透明度\n*/\nfunction colorToRgba(color, alpha) {\n    color = rgbToHex(color)\n    // 十六进制颜色值的正则表达式\n    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/\n    /* 16进制颜色转为RGB格式 */\n    let sColor = String(color).toLowerCase()\n    if (sColor && reg.test(sColor)) {\n        if (sColor.length === 4) {\n            let sColorNew = '#'\n            for (let i = 1; i < 4; i += 1) {\n                sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))\n            }\n            sColor = sColorNew\n        }\n        // 处理六位的颜色值\n        const sColorChange = []\n        for (let i = 1; i < 7; i += 2) {\n            sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))\n        }\n        // return sColorChange.join(',')\n        return `rgba(${sColorChange.join(',')},${alpha})`\n    }\n\n    return sColor\n}\n\nexport default {\n    colorGradient,\n    hexToRgb,\n    rgbToHex,\n    colorToRgba\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/function/debounce.js",
    "content": "let timeout = null\n\n/**\n * 防抖原理：一定时间内，只有最后一次操作，再过wait毫秒后才执行函数\n *\n * @param {Function} func 要执行的回调函数\n * @param {Number} wait 延时的时间\n * @param {Boolean} immediate 是否立即执行\n * @return null\n */\nfunction debounce(func, wait = 500, immediate = false) {\n    // 清除定时器\n    if (timeout !== null) clearTimeout(timeout)\n    // 立即执行，此类情况一般用不到\n    if (immediate) {\n        const callNow = !timeout\n        timeout = setTimeout(() => {\n            timeout = null\n        }, wait)\n        if (callNow) typeof func === 'function' && func()\n    } else {\n        // 设置定时器，当最后一次操作后，timeout不会再被清除，所以在延时wait毫秒后执行func回调方法\n        timeout = setTimeout(() => {\n            typeof func === 'function' && func()\n        }, wait)\n    }\n}\n\nexport default debounce\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/function/digit.js",
    "content": "let _boundaryCheckingState = true; // 是否进行越界检查的全局开关\n\n/**\n * 把错误的数据转正\n * @private\n * @example strip(0.09999999999999998)=0.1\n */\nfunction strip(num, precision = 15) {\n  return +parseFloat(Number(num).toPrecision(precision));\n}\n\n/**\n * Return digits length of a number\n * @private\n * @param {*number} num Input number\n */\nfunction digitLength(num) {\n  // Get digit length of e\n  const eSplit = num.toString().split(/[eE]/);\n  const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);\n  return len > 0 ? len : 0;\n}\n\n/**\n * 把小数转成整数,如果是小数则放大成整数\n * @private\n * @param {*number} num 输入数\n */\nfunction float2Fixed(num) {\n  if (num.toString().indexOf('e') === -1) {\n    return Number(num.toString().replace('.', ''));\n  }\n  const dLen = digitLength(num);\n  return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num);\n}\n\n/**\n * 检测数字是否越界，如果越界给出提示\n * @private\n * @param {*number} num 输入数\n */\nfunction checkBoundary(num) {\n  if (_boundaryCheckingState) {\n    if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {\n      console.warn(`${num} 超出了精度限制，结果可能不正确`);\n    }\n  }\n}\n\n/**\n * 把递归操作扁平迭代化\n * @param {number[]} arr 要操作的数字数组\n * @param {function} operation 迭代操作\n * @private\n */\nfunction iteratorOperation(arr, operation) {\n  const [num1, num2, ...others] = arr;\n  let res = operation(num1, num2);\n\n  others.forEach((num) => {\n    res = operation(res, num);\n  });\n\n  return res;\n}\n\n/**\n * 高精度乘法\n * @export\n */\nexport function times(...nums) {\n  if (nums.length > 2) {\n    return iteratorOperation(nums, times);\n  }\n\n  const [num1, num2] = nums;\n  const num1Changed = float2Fixed(num1);\n  const num2Changed = float2Fixed(num2);\n  const baseNum = digitLength(num1) + digitLength(num2);\n  const leftValue = num1Changed * num2Changed;\n\n  checkBoundary(leftValue);\n\n  return leftValue / Math.pow(10, baseNum);\n}\n\n/**\n * 高精度加法\n * @export\n */\nexport function plus(...nums) {\n  if (nums.length > 2) {\n    return iteratorOperation(nums, plus);\n  }\n\n  const [num1, num2] = nums;\n  // 取最大的小数位\n  const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));\n  // 把小数都转为整数然后再计算\n  return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;\n}\n\n/**\n * 高精度减法\n * @export\n */\nexport function minus(...nums) {\n  if (nums.length > 2) {\n    return iteratorOperation(nums, minus);\n  }\n\n  const [num1, num2] = nums;\n  const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));\n  return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;\n}\n\n/**\n * 高精度除法\n * @export\n */\nexport function divide(...nums) {\n  if (nums.length > 2) {\n    return iteratorOperation(nums, divide);\n  }\n\n  const [num1, num2] = nums;\n  const num1Changed = float2Fixed(num1);\n  const num2Changed = float2Fixed(num2);\n  checkBoundary(num1Changed);\n  checkBoundary(num2Changed);\n  // 重要，这里必须用strip进行修正\n  return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1))));\n}\n\n/**\n * 四舍五入\n * @export\n */\nexport function round(num, ratio) {\n  const base = Math.pow(10, ratio);\n  let result = divide(Math.round(Math.abs(times(num, base))), base);\n  if (num < 0 && result !== 0) {\n    result = times(result, -1);\n  }\n  // 位数不足则补0\n  return result;\n}\n\n/**\n * 是否进行边界检查，默认开启\n * @param flag 标记开关，true 为开启，false 为关闭，默认为 true\n * @export\n */\nexport function enableBoundaryChecking(flag = true) {\n  _boundaryCheckingState = flag;\n}\n\n\nexport default {\n  times,\n  plus,\n  minus,\n  divide,\n  round,\n  enableBoundaryChecking,\n};\n\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/function/index.js",
    "content": "import test from './test.js'\nimport { round } from './digit.js'\n/**\n * @description 如果value小于min，取min；如果value大于max，取max\n * @param {number} min \n * @param {number} max \n * @param {number} value\n */\nfunction range(min = 0, max = 0, value = 0) {\n\treturn Math.max(min, Math.min(max, Number(value)))\n}\n\n/**\n * @description 用于获取用户传递值的px值  如果用户传递了\"xxpx\"或者\"xxrpx\"，取出其数值部分，如果是\"xxxrpx\"还需要用过uni.upx2px进行转换\n * @param {number|string} value 用户传递值的px值\n * @param {boolean} unit \n * @returns {number|string}\n */\nfunction getPx(value, unit = false) {\n\tif (test.number(value)) {\n\t\treturn unit ? `${value}px` : Number(value)\n\t}\n\t// 如果带有rpx，先取出其数值部分，再转为px值\n\tif (/(rpx|upx)$/.test(value)) {\n\t\treturn unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))\n\t}\n\treturn unit ? `${parseInt(value)}px` : parseInt(value)\n}\n\n/**\n * @description 进行延时，以达到可以简写代码的目的 比如: await uni.$u.sleep(20)将会阻塞20ms\n * @param {number} value 堵塞时间 单位ms 毫秒\n * @returns {Promise} 返回promise\n */\nfunction sleep(value = 30) {\n\treturn new Promise((resolve) => {\n\t\tsetTimeout(() => {\n\t\t\tresolve()\n\t\t}, value)\n\t})\n}\n/**\n * @description 运行期判断平台\n * @returns {string} 返回所在平台(小写) \n * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台\n */\nfunction os() {\n\treturn uni.getSystemInfoSync().platform.toLowerCase()\n}\n/**\n * @description 获取系统信息同步接口\n * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync \n */\nfunction sys() {\n\treturn uni.getSystemInfoSync()\n}\n\n/**\n * @description 取一个区间数\n * @param {Number} min 最小值\n * @param {Number} max 最大值\n */\nfunction random(min, max) {\n\tif (min >= 0 && max > 0 && max >= min) {\n\t\tconst gab = max - min + 1\n\t\treturn Math.floor(Math.random() * gab + min)\n\t}\n\treturn 0\n}\n\n/**\n * @param {Number} len uuid的长度\n * @param {Boolean} firstU 将返回的首字母置为\"u\"\n * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制\n */\nfunction guid(len = 32, firstU = true, radix = null) {\n\tconst chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')\n\tconst uuid = []\n\tradix = radix || chars.length\n\n\tif (len) {\n\t\t// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位\n\t\tfor (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]\n\t} else {\n\t\tlet r\n\t\t// rfc4122标准要求返回的uuid中,某些位为固定的字符\n\t\tuuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'\n\t\tuuid[14] = '4'\n\n\t\tfor (let i = 0; i < 36; i++) {\n\t\t\tif (!uuid[i]) {\n\t\t\t\tr = 0 | Math.random() * 16\n\t\t\t\tuuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]\n\t\t\t}\n\t\t}\n\t}\n\t// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class\n\tif (firstU) {\n\t\tuuid.shift()\n\t\treturn `u${uuid.join('')}`\n\t}\n\treturn uuid.join('')\n}\n\n/**\n* @description 获取父组件的参数，因为支付宝小程序不支持provide/inject的写法\n   this.$parent在非H5中，可以准确获取到父组件，但是在H5中，需要多次this.$parent.$parent.xxx\n   这里默认值等于undefined有它的含义，因为最顶层元素(组件)的$parent就是undefined，意味着不传name\n   值(默认为undefined)，就是查找最顶层的$parent\n*  @param {string|undefined} name 父组件的参数名\n*/\nfunction $parent(name = undefined) {\n\tlet parent = this.$parent\n\t// 通过while历遍，这里主要是为了H5需要多层解析的问题\n\twhile (parent) {\n\t\t// 父组件\n\t\tif (parent.$options && parent.$options.name !== name) {\n\t\t\t// 如果组件的name不相等，继续上一级寻找\n\t\t\tparent = parent.$parent\n\t\t} else {\n\t\t\treturn parent\n\t\t}\n\t}\n\treturn false\n}\n\n/**\n * @description 样式转换\n * 对象转字符串，或者字符串转对象\n * @param {object | string} customStyle 需要转换的目标\n * @param {String} target 转换的目的，object-转为对象，string-转为字符串\n * @returns {object|string}\n */\nfunction addStyle(customStyle, target = 'object') {\n\t// 字符串转字符串，对象转对象情形，直接返回\n\tif (test.empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&\n\t\ttypeof(customStyle) === 'string') {\n\t\treturn customStyle\n\t}\n\t// 字符串转对象\n\tif (target === 'object') {\n\t\t// 去除字符串样式中的两端空格(中间的空格不能去掉，比如padding: 20px 0如果去掉了就错了)，空格是无用的\n\t\tcustomStyle = trim(customStyle)\n\t\t// 根据\";\"将字符串转为数组形式\n\t\tconst styleArray = customStyle.split(';')\n\t\tconst style = {}\n\t\t// 历遍数组，拼接成对象\n\t\tfor (let i = 0; i < styleArray.length; i++) {\n\t\t\t// 'font-size:20px;color:red;'，如此最后字符串有\";\"的话，会导致styleArray最后一个元素为空字符串，这里需要过滤\n\t\t\tif (styleArray[i]) {\n\t\t\t\tconst item = styleArray[i].split(':')\n\t\t\t\tstyle[trim(item[0])] = trim(item[1])\n\t\t\t}\n\t\t}\n\t\treturn style\n\t}\n\t// 这里为对象转字符串形式\n\tlet string = ''\n\tfor (const i in customStyle) {\n\t\t// 驼峰转为中划线的形式，否则css内联样式，无法识别驼峰样式属性名\n\t\tconst key = i.replace(/([A-Z])/g, '-$1').toLowerCase()\n\t\tstring += `${key}:${customStyle[i]};`\n\t}\n\t// 去除两端空格\n\treturn trim(string)\n}\n\n/**\n * @description 添加单位，如果有rpx，upx，%，px等单位结尾或者值为auto，直接返回，否则加上px单位结尾\n * @param {string|number} value 需要添加单位的值\n * @param {string} unit 添加的单位名 比如px\n */\nfunction addUnit(value = 'auto', unit = uni?.$u?.config?.unit ?? 'px') {\n\tvalue = String(value)\n\t// 用uView内置验证规则中的number判断是否为数值\n\treturn test.number(value) ? `${value}${unit}` : value\n}\n\n/**\n * @description 深度克隆\n * @param {object} obj 需要深度克隆的对象\n * @returns {*} 克隆后的对象或者原值（不是对象）\n */\nfunction deepClone(obj) {\n\t// 对常见的“非”值，直接返回原来值\n\tif ([null, undefined, NaN, false].includes(obj)) return obj\n\tif (typeof obj !== 'object' && typeof obj !== 'function') {\n\t\t// 原始类型直接返回\n\t\treturn obj\n\t}\n\tconst o = test.array(obj) ? [] : {}\n\tfor (const i in obj) {\n\t\tif (obj.hasOwnProperty(i)) {\n\t\t\to[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]\n\t\t}\n\t}\n\treturn o\n}\n\n/**\n * @description JS对象深度合并\n * @param {object} target 需要拷贝的对象\n * @param {object} source 拷贝的来源对象\n * @returns {object|boolean} 深度合并后的对象或者false（入参有不是对象）\n */\nfunction deepMerge(target = {}, source = {}) {\n\ttarget = deepClone(target)\n\tif (typeof target !== 'object' || typeof source !== 'object') return false\n\tfor (const prop in source) {\n\t\tif (!source.hasOwnProperty(prop)) continue\n\t\tif (prop in target) {\n\t\t\tif (typeof target[prop] !== 'object') {\n\t\t\t\ttarget[prop] = source[prop]\n\t\t\t} else if (typeof source[prop] !== 'object') {\n\t\t\t\ttarget[prop] = source[prop]\n\t\t\t} else if (target[prop].concat && source[prop].concat) {\n\t\t\t\ttarget[prop] = target[prop].concat(source[prop])\n\t\t\t} else {\n\t\t\t\ttarget[prop] = deepMerge(target[prop], source[prop])\n\t\t\t}\n\t\t} else {\n\t\t\ttarget[prop] = source[prop]\n\t\t}\n\t}\n\treturn target\n}\n\n/**\n * @description error提示\n * @param {*} err 错误内容\n */\nfunction error(err) {\n\t// 开发环境才提示，生产环境不会提示\n\tif (process.env.NODE_ENV === 'development') {\n\t\tconsole.error(`uView提示：${err}`)\n\t}\n}\n\n/**\n * @description 打乱数组\n * @param {array} array 需要打乱的数组\n * @returns {array} 打乱后的数组\n */\nfunction randomArray(array = []) {\n\t// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0\n\treturn array.sort(() => Math.random() - 0.5)\n}\n\n// padStart 的 polyfill，因为某些机型或情况，还无法支持es7的padStart，比如电脑版的微信小程序\n// 所以这里做一个兼容polyfill的兼容处理\nif (!String.prototype.padStart) {\n\t// 为了方便表示这里 fillString 用了ES6 的默认参数，不影响理解\n\tString.prototype.padStart = function(maxLength, fillString = ' ') {\n\t\tif (Object.prototype.toString.call(fillString) !== '[object String]') {\n\t\t\tthrow new TypeError(\n\t\t\t\t'fillString must be String'\n\t\t\t)\n\t\t}\n\t\tconst str = this\n\t\t// 返回 String(str) 这里是为了使返回的值是字符串字面量，在控制台中更符合直觉\n\t\tif (str.length >= maxLength) return String(str)\n\n\t\tconst fillLength = maxLength - str.length\n\t\tlet times = Math.ceil(fillLength / fillString.length)\n\t\twhile (times >>= 1) {\n\t\t\tfillString += fillString\n\t\t\tif (times === 1) {\n\t\t\t\tfillString += fillString\n\t\t\t}\n\t\t}\n\t\treturn fillString.slice(0, fillLength) + str\n\t}\n}\n\n/**\n * @description 格式化时间\n * @param {String|Number} dateTime 需要格式化的时间戳\n * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd\n * @returns {string} 返回格式化后的字符串\n */\n function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {\n  let date\n\t// 若传入时间为假值，则取当前时间\n  if (!dateTime) {\n    date = new Date()\n  }\n  // 若为unix秒时间戳，则转为毫秒时间戳（逻辑有点奇怪，但不敢改，以保证历史兼容）\n  else if (/^\\d{10}$/.test(dateTime?.toString().trim())) {\n    date = new Date(dateTime * 1000)\n  }\n  // 若用户传入字符串格式时间戳，new Date无法解析，需做兼容\n  else if (typeof dateTime === 'string' && /^\\d+$/.test(dateTime.trim())) {\n    date = new Date(Number(dateTime))\n  }\n\t// 处理平台性差异，在Safari/Webkit中，new Date仅支持/作为分割符的字符串时间\n\t// 处理 '2022-07-10 01:02:03'，跳过 '2022-07-10T01:02:03'\n\telse if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) {\n\t\tdate = new Date(dateTime.replace(/-/g, '/'))\n\t}\n\t// 其他都认为符合 RFC 2822 规范\n\telse {\n\t\tdate = new Date(dateTime)\n\t}\n\n\tconst timeSource = {\n\t\t'y': date.getFullYear().toString(), // 年\n\t\t'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月\n\t\t'd': date.getDate().toString().padStart(2, '0'), // 日\n\t\t'h': date.getHours().toString().padStart(2, '0'), // 时\n\t\t'M': date.getMinutes().toString().padStart(2, '0'), // 分\n\t\t's': date.getSeconds().toString().padStart(2, '0') // 秒\n\t\t// 有其他格式化字符需求可以继续添加，必须转化成字符串\n\t}\n\n  for (const key in timeSource) {\n    const [ret] = new RegExp(`${key}+`).exec(formatStr) || []\n    if (ret) {\n      // 年可能只需展示两位\n      const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0\n      formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))\n    }\n  }\n\n  return formatStr\n}\n\n/**\n * @description 时间戳转为多久之前\n * @param {String|Number} timestamp 时间戳\n * @param {String|Boolean} format \n * 格式化规则如果为时间格式字符串，超出一定时间范围，返回固定的时间格式；\n * 如果为布尔值false，无论什么时间，都返回多久以前的格式\n * @returns {string} 转化后的内容\n */\nfunction timeFrom(timestamp = null, format = 'yyyy-mm-dd') {\n\tif (timestamp == null) timestamp = Number(new Date())\n\ttimestamp = parseInt(timestamp)\n\t// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)\n\tif (timestamp.toString().length == 10) timestamp *= 1000\n\tlet timer = (new Date()).getTime() - timestamp\n\ttimer = parseInt(timer / 1000)\n\t// 如果小于5分钟,则返回\"刚刚\",其他以此类推\n\tlet tips = ''\n\tswitch (true) {\n\t\tcase timer < 300:\n\t\t\ttips = '刚刚'\n\t\t\tbreak\n\t\tcase timer >= 300 && timer < 3600:\n\t\t\ttips = `${parseInt(timer / 60)}分钟前`\n\t\t\tbreak\n\t\tcase timer >= 3600 && timer < 86400:\n\t\t\ttips = `${parseInt(timer / 3600)}小时前`\n\t\t\tbreak\n\t\tcase timer >= 86400 && timer < 2592000:\n\t\t\ttips = `${parseInt(timer / 86400)}天前`\n\t\t\tbreak\n\t\tdefault:\n\t\t\t// 如果format为false，则无论什么时间戳，都显示xx之前\n\t\t\tif (format === false) {\n\t\t\t\tif (timer >= 2592000 && timer < 365 * 86400) {\n\t\t\t\t\ttips = `${parseInt(timer / (86400 * 30))}个月前`\n\t\t\t\t} else {\n\t\t\t\t\ttips = `${parseInt(timer / (86400 * 365))}年前`\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ttips = timeFormat(timestamp, format)\n\t\t\t}\n\t}\n\treturn tips\n}\n\n/**\n * @description 去除空格\n * @param String str 需要去除空格的字符串\n * @param String pos both(左右)|left|right|all 默认both\n */\nfunction trim(str, pos = 'both') {\n\tstr = String(str)\n\tif (pos == 'both') {\n\t\treturn str.replace(/^\\s+|\\s+$/g, '')\n\t}\n\tif (pos == 'left') {\n\t\treturn str.replace(/^\\s*/, '')\n\t}\n\tif (pos == 'right') {\n\t\treturn str.replace(/(\\s*$)/g, '')\n\t}\n\tif (pos == 'all') {\n\t\treturn str.replace(/\\s+/g, '')\n\t}\n\treturn str\n}\n\n/**\n * @description 对象转url参数\n * @param {object} data,对象\n * @param {Boolean} isPrefix,是否自动加上\"?\"\n * @param {string} arrayFormat 规则 indices|brackets|repeat|comma\n */\nfunction queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {\n\tconst prefix = isPrefix ? '?' : ''\n\tconst _result = []\n\tif (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'\n\tfor (const key in data) {\n\t\tconst value = data[key]\n\t\t// 去掉为空的参数\n\t\tif (['', undefined, null].indexOf(value) >= 0) {\n\t\t\tcontinue\n\t\t}\n\t\t// 如果值为数组，另行处理\n\t\tif (value.constructor === Array) {\n\t\t\t// e.g. {ids: [1, 2, 3]}\n\t\t\tswitch (arrayFormat) {\n\t\t\t\tcase 'indices':\n\t\t\t\t\t// 结果: ids[0]=1&ids[1]=2&ids[2]=3\n\t\t\t\t\tfor (let i = 0; i < value.length; i++) {\n\t\t\t\t\t\t_result.push(`${key}[${i}]=${value[i]}`)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\tcase 'brackets':\n\t\t\t\t\t// 结果: ids[]=1&ids[]=2&ids[]=3\n\t\t\t\t\tvalue.forEach((_value) => {\n\t\t\t\t\t\t_result.push(`${key}[]=${_value}`)\n\t\t\t\t\t})\n\t\t\t\t\tbreak\n\t\t\t\tcase 'repeat':\n\t\t\t\t\t// 结果: ids=1&ids=2&ids=3\n\t\t\t\t\tvalue.forEach((_value) => {\n\t\t\t\t\t\t_result.push(`${key}=${_value}`)\n\t\t\t\t\t})\n\t\t\t\t\tbreak\n\t\t\t\tcase 'comma':\n\t\t\t\t\t// 结果: ids=1,2,3\n\t\t\t\t\tlet commaStr = ''\n\t\t\t\t\tvalue.forEach((_value) => {\n\t\t\t\t\t\tcommaStr += (commaStr ? ',' : '') + _value\n\t\t\t\t\t})\n\t\t\t\t\t_result.push(`${key}=${commaStr}`)\n\t\t\t\t\tbreak\n\t\t\t\tdefault:\n\t\t\t\t\tvalue.forEach((_value) => {\n\t\t\t\t\t\t_result.push(`${key}[]=${_value}`)\n\t\t\t\t\t})\n\t\t\t}\n\t\t} else {\n\t\t\t_result.push(`${key}=${value}`)\n\t\t}\n\t}\n\treturn _result.length ? prefix + _result.join('&') : ''\n}\n\n/**\n * 显示消息提示框\n * @param {String} title 提示的内容，长度与 icon 取值有关。\n * @param {Number} duration 提示的延迟时间，单位毫秒，默认：2000\n */\nfunction toast(title, duration = 2000) {\n\tuni.showToast({\n\t\ttitle: String(title),\n\t\ticon: 'none',\n\t\tduration\n\t})\n}\n\n/**\n * @description 根据主题type值,获取对应的图标\n * @param {String} type 主题名称,primary|info|error|warning|success\n * @param {boolean} fill 是否使用fill填充实体的图标\n */\nfunction type2icon(type = 'success', fill = false) {\n\t// 如果非预置值,默认为success\n\tif (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'\n\tlet iconName = ''\n\t// 目前(2019-12-12),info和primary使用同一个图标\n\tswitch (type) {\n\t\tcase 'primary':\n\t\t\ticonName = 'info-circle'\n\t\t\tbreak\n\t\tcase 'info':\n\t\t\ticonName = 'info-circle'\n\t\t\tbreak\n\t\tcase 'error':\n\t\t\ticonName = 'close-circle'\n\t\t\tbreak\n\t\tcase 'warning':\n\t\t\ticonName = 'error-circle'\n\t\t\tbreak\n\t\tcase 'success':\n\t\t\ticonName = 'checkmark-circle'\n\t\t\tbreak\n\t\tdefault:\n\t\t\ticonName = 'checkmark-circle'\n\t}\n\t// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的\n\tif (fill) iconName += '-fill'\n\treturn iconName\n}\n\n/**\n * @description 数字格式化\n * @param {number|string} number 要格式化的数字\n * @param {number} decimals 保留几位小数\n * @param {string} decimalPoint 小数点符号\n * @param {string} thousandsSeparator 千分位符号\n * @returns {string} 格式化后的数字\n */\nfunction priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {\n\tnumber = (`${number}`).replace(/[^0-9+-Ee.]/g, '')\n\tconst n = !isFinite(+number) ? 0 : +number\n\tconst prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)\n\tconst sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator\n\tconst dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint\n\tlet s = ''\n\n\ts = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')\n\tconst re = /(-?\\d+)(\\d{3})/\n\twhile (re.test(s[0])) {\n\t\ts[0] = s[0].replace(re, `$1${sep}$2`)\n\t}\n\t\n\tif ((s[1] || '').length < prec) {\n\t\ts[1] = s[1] || ''\n\t\ts[1] += new Array(prec - s[1].length + 1).join('0')\n\t}\n\treturn s.join(dec)\n}\n\n/**\n * @description 获取duration值\n * 如果带有ms或者s直接返回，如果大于一定值，认为是ms单位，小于一定值，认为是s单位\n * 比如以30位阈值，那么300大于30，可以理解为用户想要的是300ms，而不是想花300s去执行一个动画\n * @param {String|number} value 比如: \"1s\"|\"100ms\"|1|100\n * @param {boolean} unit  提示: 如果是false 默认返回number\n * @return {string|number} \n */\nfunction getDuration(value, unit = true) {\n\tconst valueNum = parseInt(value)\n\tif (unit) {\n\t\tif (/s$/.test(value)) return value\n\t\treturn value > 30 ? `${value}ms` : `${value}s`\n\t}\n\tif (/ms$/.test(value)) return valueNum\n\tif (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000\n\treturn valueNum\n}\n\n/**\n * @description 日期的月或日补零操作\n * @param {String} value 需要补零的值\n */\nfunction padZero(value) {\n\treturn `00${value}`.slice(-2)\n}\n\n/**\n * @description 在u-form的子组件内容发生变化，或者失去焦点时，尝试通知u-form执行校验方法\n * @param {*} instance\n * @param {*} event\n */\nfunction formValidate(instance, event) {\n\tconst formItem = uni.$u.$parent.call(instance, 'u-form-item')\n\tconst form = uni.$u.$parent.call(instance, 'u-form')\n\t// 如果发生变化的input或者textarea等，其父组件中有u-form-item或者u-form等，就执行form的validate方法\n\t// 同时将form-item的pros传递给form，让其进行精确对象验证\n\tif (formItem && form) {\n\t\tform.validateField(formItem.prop, () => {}, event)\n\t}\n}\n\n/**\n * @description 获取某个对象下的属性，用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式\n * @param {object} obj 对象\n * @param {string} key 需要获取的属性字段\n * @returns {*}\n */\nfunction getProperty(obj, key) {\n\tif (!obj) {\n\t\treturn\n\t}\n\tif (typeof key !== 'string' || key === '') {\n\t\treturn ''\n\t}\n\tif (key.indexOf('.') !== -1) {\n\t\tconst keys = key.split('.')\n\t\tlet firstObj = obj[keys[0]] || {}\n\n\t\tfor (let i = 1; i < keys.length; i++) {\n\t\t\tif (firstObj) {\n\t\t\t\tfirstObj = firstObj[keys[i]]\n\t\t\t}\n\t\t}\n\t\treturn firstObj\n\t}\n\treturn obj[key]\n}\n\n/**\n * @description 设置对象的属性值，如果'a.b.c'的形式进行设置\n * @param {object} obj 对象\n * @param {string} key 需要设置的属性\n * @param {string} value 设置的值\n */\nfunction setProperty(obj, key, value) {\n\tif (!obj) {\n\t\treturn\n\t}\n\t// 递归赋值\n\tconst inFn = function(_obj, keys, v) {\n\t\t// 最后一个属性key\n\t\tif (keys.length === 1) {\n\t\t\t_obj[keys[0]] = v\n\t\t\treturn\n\t\t}\n\t\t// 0~length-1个key\n\t\twhile (keys.length > 1) {\n\t\t\tconst k = keys[0]\n\t\t\tif (!_obj[k] || (typeof _obj[k] !== 'object')) {\n\t\t\t\t_obj[k] = {}\n\t\t\t}\n\t\t\tconst key = keys.shift()\n\t\t\t// 自调用判断是否存在属性，不存在则自动创建对象\n\t\t\tinFn(_obj[k], keys, v)\n\t\t}\n\t}\n\n\tif (typeof key !== 'string' || key === '') {\n\n\t} else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作\n\t\tconst keys = key.split('.')\n\t\tinFn(obj, keys, value)\n\t} else {\n\t\tobj[key] = value\n\t}\n}\n\n/**\n * @description 获取当前页面路径\n */\nfunction page() {\n\tconst pages = getCurrentPages()\n\t// 某些特殊情况下(比如页面进行redirectTo时的一些时机)，pages可能为空数组\n\treturn `/${pages[pages.length - 1]?.route ?? ''}`\n}\n\n/**\n * @description 获取当前路由栈实例数组\n */\nfunction pages() {\n\tconst pages = getCurrentPages()\n\treturn pages\n}\n\n/**\n * @description 修改uView内置属性值\n * @param {object} props 修改内置props属性\n * @param {object} config 修改内置config属性\n * @param {object} color 修改内置color属性\n * @param {object} zIndex 修改内置zIndex属性\n */\nfunction setConfig({\n\tprops = {},\n\tconfig = {},\n\tcolor = {},\n\tzIndex = {}\n}) {\n\tconst {\n\t\tdeepMerge,\n\t} = uni.$u\n\tuni.$u.config = deepMerge(uni.$u.config, config)\n\tuni.$u.props = deepMerge(uni.$u.props, props)\n\tuni.$u.color = deepMerge(uni.$u.color, color)\n\tuni.$u.zIndex = deepMerge(uni.$u.zIndex, zIndex)\n}\n\nexport default {\n\trange,\n\tgetPx,\n\tsleep,\n\tos,\n\tsys,\n\trandom,\n\tguid,\n\t$parent,\n\taddStyle,\n\taddUnit,\n\tdeepClone,\n\tdeepMerge,\n\terror,\n\trandomArray,\n\ttimeFormat,\n\ttimeFrom,\n\ttrim,\n\tqueryParams,\n\ttoast,\n\ttype2icon,\n\tpriceFormat,\n\tgetDuration,\n\tpadZero,\n\tformValidate,\n\tgetProperty,\n\tsetProperty,\n\tpage,\n\tpages,\n\tsetConfig\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/function/platform.js",
    "content": "/**\n * 注意：\n * 此部分内容，在vue-cli模式下，需要在vue.config.js加入如下内容才有效：\n * module.exports = {\n *     transpileDependencies: ['uview-v2']\n * }\n */\n\nlet platform = 'none'\n\n// #ifdef VUE3\nplatform = 'vue3'\n// #endif\n\n// #ifdef VUE2\nplatform = 'vue2'\n// #endif\n\n// #ifdef APP-PLUS\nplatform = 'plus'\n// #endif\n\n// #ifdef APP-NVUE\nplatform = 'nvue'\n// #endif\n\n// #ifdef H5\nplatform = 'h5'\n// #endif\n\n// #ifdef MP-WEIXIN\nplatform = 'weixin'\n// #endif\n\n// #ifdef MP-ALIPAY\nplatform = 'alipay'\n// #endif\n\n// #ifdef MP-BAIDU\nplatform = 'baidu'\n// #endif\n\n// #ifdef MP-TOUTIAO\nplatform = 'toutiao'\n// #endif\n\n// #ifdef MP-QQ\nplatform = 'qq'\n// #endif\n\n// #ifdef MP-KUAISHOU\nplatform = 'kuaishou'\n// #endif\n\n// #ifdef MP-360\nplatform = '360'\n// #endif\n\n// #ifdef MP\nplatform = 'mp'\n// #endif\n\n// #ifdef QUICKAPP-WEBVIEW\nplatform = 'quickapp-webview'\n// #endif\n\n// #ifdef QUICKAPP-WEBVIEW-HUAWEI\nplatform = 'quickapp-webview-huawei'\n// #endif\n\n// #ifdef QUICKAPP-WEBVIEW-UNION\nplatform = 'quckapp-webview-union'\n// #endif\n\nexport default platform\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/function/test.js",
    "content": "/**\n * 验证电子邮箱格式\n */\nfunction email(value) {\n    return /^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$/.test(value)\n}\n\n/**\n * 验证手机格式\n */\nfunction mobile(value) {\n    return /^1([3589]\\d|4[5-9]|6[1-2,4-7]|7[0-8])\\d{8}$/.test(value)\n}\n\n/**\n * 验证URL格式\n */\nfunction url(value) {\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_!~*'().;?:@&=+$,%#-]+)+\\/?)$/\n        .test(value)\n}\n\n/**\n * 验证日期格式\n */\nfunction date(value) {\n    if (!value) return false\n    // 判断是否数值或者字符串数值(意味着为时间戳)，转为数值，否则new Date无法识别字符串时间戳\n    if (number(value)) value = +value\n    return !/Invalid|NaN/.test(new Date(value).toString())\n}\n\n/**\n * 验证ISO类型的日期格式\n */\nfunction dateISO(value) {\n    return /^\\d{4}[\\/\\-](0?[1-9]|1[012])[\\/\\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)\n}\n\n/**\n * 验证十进制数字\n */\nfunction number(value) {\n    return /^[\\+-]?(\\d+\\.?\\d*|\\.\\d+|\\d\\.\\d+e\\+\\d+)$/.test(value)\n}\n\n/**\n * 验证字符串\n */\nfunction string(value) {\n    return typeof value === 'string'\n}\n\n/**\n * 验证整数\n */\nfunction digits(value) {\n    return /^\\d+$/.test(value)\n}\n\n/**\n * 验证身份证号码\n */\nfunction idCard(value) {\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(\n        value\n    )\n}\n\n/**\n * 是否车牌号\n */\nfunction carNo(value) {\n    // 新能源车牌\n    const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/\n    // 旧车牌\n    const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/\n    if (value.length === 7) {\n        return creg.test(value)\n    } if (value.length === 8) {\n        return xreg.test(value)\n    }\n    return false\n}\n\n/**\n * 金额,只允许2位小数\n */\nfunction amount(value) {\n    // 金额，只允许保留两位小数\n    return /^[1-9]\\d*(,\\d{3})*(\\.\\d{1,2})?$|^0\\.\\d{1,2}$/.test(value)\n}\n\n/**\n * 中文\n */\nfunction chinese(value) {\n    const reg = /^[\\u4e00-\\u9fa5]+$/gi\n    return reg.test(value)\n}\n\n/**\n * 只能输入字母\n */\nfunction letter(value) {\n    return /^[a-zA-Z]*$/.test(value)\n}\n\n/**\n * 只能是字母或者数字\n */\nfunction enOrNum(value) {\n    // 英文或者数字\n    const reg = /^[0-9a-zA-Z]*$/g\n    return reg.test(value)\n}\n\n/**\n * 验证是否包含某个值\n */\nfunction contains(value, param) {\n    return value.indexOf(param) >= 0\n}\n\n/**\n * 验证一个值范围[min, max]\n */\nfunction range(value, param) {\n    return value >= param[0] && value <= param[1]\n}\n\n/**\n * 验证一个长度范围[min, max]\n */\nfunction rangeLength(value, param) {\n    return value.length >= param[0] && value.length <= param[1]\n}\n\n/**\n * 是否固定电话\n */\nfunction landline(value) {\n    const reg = /^\\d{3,4}-\\d{7,8}(-\\d{3,4})?$/\n    return reg.test(value)\n}\n\n/**\n * 判断是否为空\n */\nfunction empty(value) {\n    switch (typeof value) {\n    case 'undefined':\n        return true\n    case 'string':\n        if (value.replace(/(^[ \\t\\n\\r]*)|([ \\t\\n\\r]*$)/g, '').length == 0) return true\n        break\n    case 'boolean':\n        if (!value) return true\n        break\n    case 'number':\n        if (value === 0 || isNaN(value)) return true\n        break\n    case 'object':\n        if (value === null || value.length === 0) return true\n        for (const i in value) {\n            return false\n        }\n        return true\n    }\n    return false\n}\n\n/**\n * 是否json字符串\n */\nfunction jsonString(value) {\n    if (typeof value === 'string') {\n        try {\n            const obj = JSON.parse(value)\n            if (typeof obj === 'object' && obj) {\n                return true\n            }\n            return false\n        } catch (e) {\n            return false\n        }\n    }\n    return false\n}\n\n/**\n * 是否数组\n */\nfunction array(value) {\n    if (typeof Array.isArray === 'function') {\n        return Array.isArray(value)\n    }\n    return Object.prototype.toString.call(value) === '[object Array]'\n}\n\n/**\n * 是否对象\n */\nfunction object(value) {\n    return Object.prototype.toString.call(value) === '[object Object]'\n}\n\n/**\n * 是否短信验证码\n */\nfunction code(value, len = 6) {\n    return new RegExp(`^\\\\d{${len}}$`).test(value)\n}\n\n/**\n * 是否函数方法\n * @param {Object} value\n */\nfunction func(value) {\n    return typeof value === 'function'\n}\n\n/**\n * 是否promise对象\n * @param {Object} value\n */\nfunction promise(value) {\n    return object(value) && func(value.then) && func(value.catch)\n}\n\n/** 是否图片格式\n * @param {Object} value\n */\nfunction image(value) {\n    const newValue = value.split('?')[0]\n    const IMAGE_REGEXP = /\\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i\n    return IMAGE_REGEXP.test(newValue)\n}\n\n/**\n * 是否视频格式\n * @param {Object} value\n */\nfunction video(value) {\n    const VIDEO_REGEXP = /\\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i\n    return VIDEO_REGEXP.test(value)\n}\n\n/**\n * 是否为正则对象\n * @param {Object}\n * @return {Boolean}\n */\nfunction regExp(o) {\n    return o && Object.prototype.toString.call(o) === '[object RegExp]'\n}\n\nexport default {\n    email,\n    mobile,\n    url,\n    date,\n    dateISO,\n    number,\n    digits,\n    idCard,\n    carNo,\n    amount,\n    chinese,\n    letter,\n    enOrNum,\n    contains,\n    range,\n    rangeLength,\n    empty,\n    isEmpty: empty,\n    jsonString,\n    landline,\n    object,\n    array,\n    code,\n    func,\n    promise,\n    video,\n    image,\n    regExp,\n    string\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/function/throttle.js",
    "content": "let timer; let\n    flag\n/**\n * 节流原理：在一定时间内，只能触发一次\n *\n * @param {Function} func 要执行的回调函数\n * @param {Number} wait 延时的时间\n * @param {Boolean} immediate 是否立即执行\n * @return null\n */\nfunction throttle(func, wait = 500, immediate = true) {\n    if (immediate) {\n        if (!flag) {\n            flag = true\n            // 如果是立即执行，则在wait毫秒内开始时执行\n            typeof func === 'function' && func()\n            timer = setTimeout(() => {\n                flag = false\n            }, wait)\n        }\n    } else if (!flag) {\n        flag = true\n        // 如果是非立即执行，则在wait毫秒内的结束处执行\n        timer = setTimeout(() => {\n            flag = false\n            typeof func === 'function' && func()\n        }, wait)\n    }\n}\nexport default throttle\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/adapters/index.js",
    "content": "import buildURL from '../helpers/buildURL'\nimport buildFullPath from '../core/buildFullPath'\nimport settle from '../core/settle'\nimport { isUndefined } from '../utils'\n\n/**\n * 返回可选值存在的配置\n * @param {Array} keys - 可选值数组\n * @param {Object} config2 - 配置\n * @return {{}} - 存在的配置项\n */\nconst mergeKeys = (keys, config2) => {\n    const config = {}\n    keys.forEach((prop) => {\n        if (!isUndefined(config2[prop])) {\n            config[prop] = config2[prop]\n        }\n    })\n    return config\n}\nexport default (config) => new Promise((resolve, reject) => {\n    const fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params)\n    const _config = {\n        url: fullPath,\n        header: config.header,\n        complete: (response) => {\n            config.fullPath = fullPath\n            response.config = config\n            try {\n                // 对可能字符串不是json 的情况容错\n                if (typeof response.data === 'string') {\n                    response.data = JSON.parse(response.data)\n                }\n                // eslint-disable-next-line no-empty\n            } catch (e) {\n            }\n            settle(resolve, reject, response)\n        }\n    }\n    let requestTask\n    if (config.method === 'UPLOAD') {\n        delete _config.header['content-type']\n        delete _config.header['Content-Type']\n        const otherConfig = {\n        // #ifdef MP-ALIPAY\n            fileType: config.fileType,\n            // #endif\n            filePath: config.filePath,\n            name: config.name\n        }\n        const optionalKeys = [\n        // #ifdef APP-PLUS || H5\n            'files',\n            // #endif\n            // #ifdef H5\n            'file',\n            // #endif\n            // #ifdef H5 || APP-PLUS\n            'timeout',\n            // #endif\n            'formData'\n        ]\n        requestTask = uni.uploadFile({ ..._config, ...otherConfig, ...mergeKeys(optionalKeys, config) })\n    } else if (config.method === 'DOWNLOAD') {\n        // #ifdef H5 || APP-PLUS\n        if (!isUndefined(config.timeout)) {\n            _config.timeout = config.timeout\n        }\n        // #endif\n        requestTask = uni.downloadFile(_config)\n    } else {\n        const optionalKeys = [\n            'data',\n            'method',\n            // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN\n            'timeout',\n            // #endif\n            'dataType',\n            // #ifndef MP-ALIPAY\n            'responseType',\n            // #endif\n            // #ifdef APP-PLUS\n            'sslVerify',\n            // #endif\n            // #ifdef H5\n            'withCredentials',\n            // #endif\n            // #ifdef APP-PLUS\n            'firstIpv4'\n        // #endif\n        ]\n        requestTask = uni.request({ ..._config, ...mergeKeys(optionalKeys, config) })\n    }\n    if (config.getTask) {\n        config.getTask(requestTask, config)\n    }\n})\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/core/InterceptorManager.js",
    "content": "'use strict'\n\nfunction InterceptorManager() {\n    this.handlers = []\n}\n\n/**\n * Add a new interceptor to the stack\n *\n * @param {Function} fulfilled The function to handle `then` for a `Promise`\n * @param {Function} rejected The function to handle `reject` for a `Promise`\n *\n * @return {Number} An ID used to remove interceptor later\n */\nInterceptorManager.prototype.use = function use(fulfilled, rejected) {\n    this.handlers.push({\n        fulfilled,\n        rejected\n    })\n    return this.handlers.length - 1\n}\n\n/**\n * Remove an interceptor from the stack\n *\n * @param {Number} id The ID that was returned by `use`\n */\nInterceptorManager.prototype.eject = function eject(id) {\n    if (this.handlers[id]) {\n        this.handlers[id] = null\n    }\n}\n\n/**\n * Iterate over all the registered interceptors\n *\n * This method is particularly useful for skipping over any\n * interceptors that may have become `null` calling `eject`.\n *\n * @param {Function} fn The function to call for each interceptor\n */\nInterceptorManager.prototype.forEach = function forEach(fn) {\n    this.handlers.forEach((h) => {\n        if (h !== null) {\n            fn(h)\n        }\n    })\n}\n\nexport default InterceptorManager\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/core/Request.js",
    "content": "/**\n * @Class Request\n * @description luch-request http请求插件\n * @version 3.0.7\n * @Author lu-ch\n * @Date 2021-09-04\n * @Email webwork.s@qq.com\n * 文档: https://www.quanzhan.co/luch-request/\n * github: https://github.com/lei-mu/luch-request\n * DCloud: http://ext.dcloud.net.cn/plugin?id=392\n * HBuilderX: beat-3.0.4 alpha-3.0.4\n */\n\nimport dispatchRequest from './dispatchRequest'\nimport InterceptorManager from './InterceptorManager'\nimport mergeConfig from './mergeConfig'\nimport defaults from './defaults'\nimport { isPlainObject } from '../utils'\nimport clone from '../utils/clone'\n\nexport default class Request {\n    /**\n   * @param {Object} arg - 全局配置\n   * @param {String} arg.baseURL - 全局根路径\n   * @param {Object} arg.header - 全局header\n   * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式\n   * @param {String} arg.dataType = [json] - 全局默认的dataType\n   * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持\n   * @param {Object} arg.custom - 全局默认的自定义参数\n   * @param {Number} arg.timeout - 全局默认的超时时间，单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序（2.10.0）、支付宝小程序\n   * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持（HBuilderX 2.3.3+）\n   * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证（cookies）。默认false。仅H5支持（HBuilderX 2.6.15+）\n   * @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+)\n   * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300\n   */\n    constructor(arg = {}) {\n        if (!isPlainObject(arg)) {\n            arg = {}\n            console.warn('设置全局参数必须接收一个Object')\n        }\n        this.config = clone({ ...defaults, ...arg })\n        this.interceptors = {\n            request: new InterceptorManager(),\n            response: new InterceptorManager()\n        }\n    }\n\n    /**\n   * @Function\n   * @param {Request~setConfigCallback} f - 设置全局默认配置\n   */\n    setConfig(f) {\n        this.config = f(this.config)\n    }\n\n    middleware(config) {\n        config = mergeConfig(this.config, config)\n        const chain = [dispatchRequest, undefined]\n        let promise = Promise.resolve(config)\n\n        this.interceptors.request.forEach((interceptor) => {\n            chain.unshift(interceptor.fulfilled, interceptor.rejected)\n        })\n\n        this.interceptors.response.forEach((interceptor) => {\n            chain.push(interceptor.fulfilled, interceptor.rejected)\n        })\n\n        while (chain.length) {\n            promise = promise.then(chain.shift(), chain.shift())\n        }\n\n        return promise\n    }\n\n    /**\n   * @Function\n   * @param {Object} config - 请求配置项\n   * @prop {String} options.url - 请求路径\n   * @prop {Object} options.data - 请求参数\n   * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型\n   * @prop {Object} [options.dataType = config.dataType] - 如果设为 json，会尝试对返回的数据做一次 JSON.parse\n   * @prop {Object} [options.header = config.header] - 请求header\n   * @prop {Object} [options.method = config.method] - 请求方法\n   * @returns {Promise<unknown>}\n   */\n    request(config = {}) {\n        return this.middleware(config)\n    }\n\n    get(url, options = {}) {\n        return this.middleware({\n            url,\n            method: 'GET',\n            ...options\n        })\n    }\n\n    post(url, data, options = {}) {\n        return this.middleware({\n            url,\n            data,\n            method: 'POST',\n            ...options\n        })\n    }\n\n    // #ifndef MP-ALIPAY\n    put(url, data, options = {}) {\n        return this.middleware({\n            url,\n            data,\n            method: 'PUT',\n            ...options\n        })\n    }\n\n    // #endif\n\n    // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU\n    delete(url, data, options = {}) {\n        return this.middleware({\n            url,\n            data,\n            method: 'DELETE',\n            ...options\n        })\n    }\n\n    // #endif\n\n    // #ifdef H5 || MP-WEIXIN\n    connect(url, data, options = {}) {\n        return this.middleware({\n            url,\n            data,\n            method: 'CONNECT',\n            ...options\n        })\n    }\n\n    // #endif\n\n    // #ifdef  H5 || MP-WEIXIN || MP-BAIDU\n    head(url, data, options = {}) {\n        return this.middleware({\n            url,\n            data,\n            method: 'HEAD',\n            ...options\n        })\n    }\n\n    // #endif\n\n    // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU\n    options(url, data, options = {}) {\n        return this.middleware({\n            url,\n            data,\n            method: 'OPTIONS',\n            ...options\n        })\n    }\n\n    // #endif\n\n    // #ifdef H5 || MP-WEIXIN\n    trace(url, data, options = {}) {\n        return this.middleware({\n            url,\n            data,\n            method: 'TRACE',\n            ...options\n        })\n    }\n\n    // #endif\n\n    upload(url, config = {}) {\n        config.url = url\n        config.method = 'UPLOAD'\n        return this.middleware(config)\n    }\n\n    download(url, config = {}) {\n        config.url = url\n        config.method = 'DOWNLOAD'\n        return this.middleware(config)\n    }\n}\n\n/**\n * setConfig回调\n * @return {Object} - 返回操作后的config\n * @callback Request~setConfigCallback\n * @param {Object} config - 全局默认config\n */\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/core/buildFullPath.js",
    "content": "'use strict'\n\nimport isAbsoluteURL from '../helpers/isAbsoluteURL'\nimport combineURLs from '../helpers/combineURLs'\n\n/**\n * Creates a new URL by combining the baseURL with the requestedURL,\n * only when the requestedURL is not already an absolute URL.\n * If the requestURL is absolute, this function returns the requestedURL untouched.\n *\n * @param {string} baseURL The base URL\n * @param {string} requestedURL Absolute or relative URL to combine\n * @returns {string} The combined full path\n */\nexport default function buildFullPath(baseURL, requestedURL) {\n    if (baseURL && !isAbsoluteURL(requestedURL)) {\n        return combineURLs(baseURL, requestedURL)\n    }\n    return requestedURL\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/core/defaults.js",
    "content": "/**\n * 默认的全局配置\n */\n\nexport default {\n    baseURL: '',\n    header: {},\n    method: 'GET',\n    dataType: 'json',\n    // #ifndef MP-ALIPAY\n    responseType: 'text',\n    // #endif\n    custom: {},\n    // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN\n    timeout: 60000,\n    // #endif\n    // #ifdef APP-PLUS\n    sslVerify: true,\n    // #endif\n    // #ifdef H5\n    withCredentials: false,\n    // #endif\n    // #ifdef APP-PLUS\n    firstIpv4: false,\n    // #endif\n    validateStatus: function validateStatus(status) {\n        return status >= 200 && status < 300\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/core/dispatchRequest.js",
    "content": "import adapter from '../adapters/index'\n\nexport default (config) => adapter(config)\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/core/mergeConfig.js",
    "content": "import { deepMerge, isUndefined } from '../utils'\n\n/**\n * 合并局部配置优先的配置，如果局部有该配置项则用局部，如果全局有该配置项则用全局\n * @param {Array} keys - 配置项\n * @param {Object} globalsConfig - 当前的全局配置\n * @param {Object} config2 - 局部配置\n * @return {{}}\n */\nconst mergeKeys = (keys, globalsConfig, config2) => {\n    const config = {}\n    keys.forEach((prop) => {\n        if (!isUndefined(config2[prop])) {\n            config[prop] = config2[prop]\n        } else if (!isUndefined(globalsConfig[prop])) {\n            config[prop] = globalsConfig[prop]\n        }\n    })\n    return config\n}\n/**\n *\n * @param globalsConfig - 当前实例的全局配置\n * @param config2 - 当前的局部配置\n * @return - 合并后的配置\n */\nexport default (globalsConfig, config2 = {}) => {\n    const method = config2.method || globalsConfig.method || 'GET'\n    let config = {\n        baseURL: globalsConfig.baseURL || '',\n        method,\n        url: config2.url || '',\n        params: config2.params || {},\n        custom: { ...(globalsConfig.custom || {}), ...(config2.custom || {}) },\n        header: deepMerge(globalsConfig.header || {}, config2.header || {})\n    }\n    const defaultToConfig2Keys = ['getTask', 'validateStatus']\n    config = { ...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2) }\n\n    // eslint-disable-next-line no-empty\n    if (method === 'DOWNLOAD') {\n    // #ifdef H5 || APP-PLUS\n        if (!isUndefined(config2.timeout)) {\n            config.timeout = config2.timeout\n        } else if (!isUndefined(globalsConfig.timeout)) {\n            config.timeout = globalsConfig.timeout\n        }\n    // #endif\n    } else if (method === 'UPLOAD') {\n        delete config.header['content-type']\n        delete config.header['Content-Type']\n        const uploadKeys = [\n            // #ifdef APP-PLUS || H5\n            'files',\n            // #endif\n            // #ifdef MP-ALIPAY\n            'fileType',\n            // #endif\n            // #ifdef H5\n            'file',\n            // #endif\n            'filePath',\n            'name',\n            // #ifdef H5 || APP-PLUS\n            'timeout',\n            // #endif\n            'formData'\n        ]\n        uploadKeys.forEach((prop) => {\n            if (!isUndefined(config2[prop])) {\n                config[prop] = config2[prop]\n            }\n        })\n        // #ifdef H5 || APP-PLUS\n        if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) {\n            config.timeout = globalsConfig.timeout\n        }\n    // #endif\n    } else {\n        const defaultsKeys = [\n            'data',\n            // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN\n            'timeout',\n            // #endif\n            'dataType',\n            // #ifndef MP-ALIPAY\n            'responseType',\n            // #endif\n            // #ifdef APP-PLUS\n            'sslVerify',\n            // #endif\n            // #ifdef H5\n            'withCredentials',\n            // #endif\n            // #ifdef APP-PLUS\n            'firstIpv4'\n            // #endif\n        ]\n        config = { ...config, ...mergeKeys(defaultsKeys, globalsConfig, config2) }\n    }\n\n    return config\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/core/settle.js",
    "content": "/**\n * Resolve or reject a Promise based on response status.\n *\n * @param {Function} resolve A function that resolves the promise.\n * @param {Function} reject A function that rejects the promise.\n * @param {object} response The response.\n */\nexport default function settle(resolve, reject, response) {\n    const { validateStatus } = response.config\n    const status = response.statusCode\n    if (status && (!validateStatus || validateStatus(status))) {\n        resolve(response)\n    } else {\n        reject(response)\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/helpers/buildURL.js",
    "content": "'use strict'\n\nimport * as utils from '../utils'\n\nfunction encode(val) {\n    return encodeURIComponent(val)\n        .replace(/%40/gi, '@')\n        .replace(/%3A/gi, ':')\n        .replace(/%24/g, '$')\n        .replace(/%2C/gi, ',')\n        .replace(/%20/g, '+')\n        .replace(/%5B/gi, '[')\n        .replace(/%5D/gi, ']')\n}\n\n/**\n * Build a URL by appending params to the end\n *\n * @param {string} url The base of the url (e.g., http://www.google.com)\n * @param {object} [params] The params to be appended\n * @returns {string} The formatted url\n */\nexport default function buildURL(url, params) {\n    /* eslint no-param-reassign:0 */\n    if (!params) {\n        return url\n    }\n\n    let serializedParams\n    if (utils.isURLSearchParams(params)) {\n        serializedParams = params.toString()\n    } else {\n        const parts = []\n\n        utils.forEach(params, (val, key) => {\n            if (val === null || typeof val === 'undefined') {\n                return\n            }\n\n            if (utils.isArray(val)) {\n                key = `${key}[]`\n            } else {\n                val = [val]\n            }\n\n            utils.forEach(val, (v) => {\n                if (utils.isDate(v)) {\n                    v = v.toISOString()\n                } else if (utils.isObject(v)) {\n                    v = JSON.stringify(v)\n                }\n                parts.push(`${encode(key)}=${encode(v)}`)\n            })\n        })\n\n        serializedParams = parts.join('&')\n    }\n\n    if (serializedParams) {\n        const hashmarkIndex = url.indexOf('#')\n        if (hashmarkIndex !== -1) {\n            url = url.slice(0, hashmarkIndex)\n        }\n\n        url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams\n    }\n\n    return url\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/helpers/combineURLs.js",
    "content": "'use strict'\n\n/**\n * Creates a new URL by combining the specified URLs\n *\n * @param {string} baseURL The base URL\n * @param {string} relativeURL The relative URL\n * @returns {string} The combined URL\n */\nexport default function combineURLs(baseURL, relativeURL) {\n    return relativeURL\n        ? `${baseURL.replace(/\\/+$/, '')}/${relativeURL.replace(/^\\/+/, '')}`\n        : baseURL\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/helpers/isAbsoluteURL.js",
    "content": "'use strict'\n\n/**\n * Determines whether the specified URL is absolute\n *\n * @param {string} url The URL to test\n * @returns {boolean} True if the specified URL is absolute, otherwise false\n */\nexport default function isAbsoluteURL(url) {\n    // A URL is considered absolute if it begins with \"<scheme>://\" or \"//\" (protocol-relative URL).\n    // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed\n    // by any combination of letters, digits, plus, period, or hyphen.\n    return /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(url)\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/index.d.ts",
    "content": "type AnyObject = Record<string | number | symbol, any>\ntype HttpPromise<T> = Promise<HttpResponse<T>>;\ntype Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask\nexport interface RequestTask {\n  abort: () => void;\n  offHeadersReceived: () => void;\n  onHeadersReceived: () => void;\n}\nexport interface HttpRequestConfig<T = Tasks> {\n  /** 请求基地址 */\n  baseURL?: string;\n  /** 请求服务器接口地址 */\n  url?: string;\n\n  /** 请求查询参数，自动拼接为查询字符串 */\n  params?: AnyObject;\n  /** 请求体参数 */\n  data?: AnyObject;\n\n  /** 文件对应的 key */\n  name?: string;\n  /** HTTP 请求中其他额外的 form data */\n  formData?: AnyObject;\n  /** 要上传文件资源的路径。 */\n  filePath?: string;\n  /** 需要上传的文件列表。使用 files 时，filePath 和 name 不生效，App、H5（ 2.6.15+） */\n  files?: Array<{\n    name?: string;\n    file?: File;\n    uri: string;\n  }>;\n  /** 要上传的文件对象，仅H5（2.6.15+）支持 */\n  file?: File;\n\n  /** 请求头信息 */\n  header?: AnyObject;\n  /** 请求方式 */\n  method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"CONNECT\" | \"HEAD\" | \"OPTIONS\" | \"TRACE\" | \"UPLOAD\" | \"DOWNLOAD\";\n  /** 如果设为 json，会尝试对返回的数据做一次 JSON.parse */\n  dataType?: string;\n  /** 设置响应的数据类型，支付宝小程序不支持 */\n  responseType?: \"text\" | \"arraybuffer\";\n  /** 自定义参数 */\n  custom?: AnyObject;\n  /** 超时时间，仅微信小程序（2.10.0）、支付宝小程序支持 */\n  timeout?: number;\n  /** DNS解析时优先使用ipv4，仅 App-Android 支持 (HBuilderX 2.8.0+) */\n  firstIpv4?: boolean;\n  /** 验证 ssl 证书 仅5+App安卓端支持（HBuilderX 2.3.3+） */\n  sslVerify?: boolean;\n  /** 跨域请求时是否携带凭证（cookies）仅H5支持（HBuilderX 2.6.15+） */\n  withCredentials?: boolean;\n\n  /** 返回当前请求的task, options。请勿在此处修改options。 */\n  getTask?: (task: T, options: HttpRequestConfig<T>) => void;\n  /**  全局自定义验证器 */\n  validateStatus?: (statusCode: number) => boolean | void;\n}\nexport interface HttpResponse<T = any> {\n  config: HttpRequestConfig;\n  statusCode: number;\n  cookies: Array<string>;\n  data: T;\n  errMsg: string;\n  header: AnyObject;\n}\nexport interface HttpUploadResponse<T = any> {\n  config: HttpRequestConfig;\n  statusCode: number;\n  data: T;\n  errMsg: string;\n}\nexport interface HttpDownloadResponse extends HttpResponse {\n  tempFilePath: string;\n}\nexport interface HttpError {\n  config: HttpRequestConfig;\n  statusCode?: number;\n  cookies?: Array<string>;\n  data?: any;\n  errMsg: string;\n  header?: AnyObject;\n}\nexport interface HttpInterceptorManager<V, E = V> {\n  use(\n    onFulfilled?: (config: V) => Promise<V> | V,\n    onRejected?: (config: E) => Promise<E> | E\n  ): void;\n  eject(id: number): void;\n}\nexport abstract class HttpRequestAbstract {\n  constructor(config?: HttpRequestConfig);\n  config: HttpRequestConfig;\n  interceptors: {\n    request: HttpInterceptorManager<HttpRequestConfig, HttpRequestConfig>;\n    response: HttpInterceptorManager<HttpResponse, HttpError>;\n  }\n  middleware<T = any>(config: HttpRequestConfig): HttpPromise<T>;\n  request<T = any>(config: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n  get<T = any>(url: string, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n  upload<T = any>(url: string, config?: HttpRequestConfig<UniApp.UploadTask>): HttpPromise<T>;\n  delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n  head<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n  post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n  put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n  connect<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n  options<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n  trace<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\n\n  download(url: string, config?: HttpRequestConfig<UniApp.DownloadTask>): Promise<HttpDownloadResponse>;\n\n  setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void;\n}\n\ndeclare class HttpRequest extends HttpRequestAbstract { }\nexport default HttpRequest;\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/index.js",
    "content": "import Request from './core/Request'\n\nexport default Request\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/utils/clone.js",
    "content": "/* eslint-disable */\nvar clone = (function() {\n  'use strict';\n\n  function _instanceof(obj, type) {\n    return type != null && obj instanceof type;\n  }\n\n  var nativeMap;\n  try {\n    nativeMap = Map;\n  } catch(_) {\n    // maybe a reference error because no `Map`. Give it a dummy value that no\n    // value will ever be an instanceof.\n    nativeMap = function() {};\n  }\n\n  var nativeSet;\n  try {\n    nativeSet = Set;\n  } catch(_) {\n    nativeSet = function() {};\n  }\n\n  var nativePromise;\n  try {\n    nativePromise = Promise;\n  } catch(_) {\n    nativePromise = function() {};\n  }\n\n  /**\n   * Clones (copies) an Object using deep copying.\n   *\n   * This function supports circular references by default, but if you are certain\n   * there are no circular references in your object, you can save some CPU time\n   * by calling clone(obj, false).\n   *\n   * Caution: if `circular` is false and `parent` contains circular references,\n   * your program may enter an infinite loop and crash.\n   *\n   * @param `parent` - the object to be cloned\n   * @param `circular` - set to true if the object to be cloned may contain\n   *    circular references. (optional - true by default)\n   * @param `depth` - set to a number if the object is only to be cloned to\n   *    a particular depth. (optional - defaults to Infinity)\n   * @param `prototype` - sets the prototype to be used when cloning an object.\n   *    (optional - defaults to parent prototype).\n   * @param `includeNonEnumerable` - set to true if the non-enumerable properties\n   *    should be cloned as well. Non-enumerable properties on the prototype\n   *    chain will be ignored. (optional - false by default)\n   */\n  function clone(parent, circular, depth, prototype, includeNonEnumerable) {\n    if (typeof circular === 'object') {\n      depth = circular.depth;\n      prototype = circular.prototype;\n      includeNonEnumerable = circular.includeNonEnumerable;\n      circular = circular.circular;\n    }\n    // maintain two arrays for circular references, where corresponding parents\n    // and children have the same index\n    var allParents = [];\n    var allChildren = [];\n\n    var useBuffer = typeof Buffer != 'undefined';\n\n    if (typeof circular == 'undefined')\n      circular = true;\n\n    if (typeof depth == 'undefined')\n      depth = Infinity;\n\n    // recurse this function so we don't reset allParents and allChildren\n    function _clone(parent, depth) {\n      // cloning null always returns null\n      if (parent === null)\n        return null;\n\n      if (depth === 0)\n        return parent;\n\n      var child;\n      var proto;\n      if (typeof parent != 'object') {\n        return parent;\n      }\n\n      if (_instanceof(parent, nativeMap)) {\n        child = new nativeMap();\n      } else if (_instanceof(parent, nativeSet)) {\n        child = new nativeSet();\n      } else if (_instanceof(parent, nativePromise)) {\n        child = new nativePromise(function (resolve, reject) {\n          parent.then(function(value) {\n            resolve(_clone(value, depth - 1));\n          }, function(err) {\n            reject(_clone(err, depth - 1));\n          });\n        });\n      } else if (clone.__isArray(parent)) {\n        child = [];\n      } else if (clone.__isRegExp(parent)) {\n        child = new RegExp(parent.source, __getRegExpFlags(parent));\n        if (parent.lastIndex) child.lastIndex = parent.lastIndex;\n      } else if (clone.__isDate(parent)) {\n        child = new Date(parent.getTime());\n      } else if (useBuffer && Buffer.isBuffer(parent)) {\n        if (Buffer.from) {\n          // Node.js >= 5.10.0\n          child = Buffer.from(parent);\n        } else {\n          // Older Node.js versions\n          child = new Buffer(parent.length);\n          parent.copy(child);\n        }\n        return child;\n      } else if (_instanceof(parent, Error)) {\n        child = Object.create(parent);\n      } else {\n        if (typeof prototype == 'undefined') {\n          proto = Object.getPrototypeOf(parent);\n          child = Object.create(proto);\n        }\n        else {\n          child = Object.create(prototype);\n          proto = prototype;\n        }\n      }\n\n      if (circular) {\n        var index = allParents.indexOf(parent);\n\n        if (index != -1) {\n          return allChildren[index];\n        }\n        allParents.push(parent);\n        allChildren.push(child);\n      }\n\n      if (_instanceof(parent, nativeMap)) {\n        parent.forEach(function(value, key) {\n          var keyChild = _clone(key, depth - 1);\n          var valueChild = _clone(value, depth - 1);\n          child.set(keyChild, valueChild);\n        });\n      }\n      if (_instanceof(parent, nativeSet)) {\n        parent.forEach(function(value) {\n          var entryChild = _clone(value, depth - 1);\n          child.add(entryChild);\n        });\n      }\n\n      for (var i in parent) {\n        var attrs = Object.getOwnPropertyDescriptor(parent, i);\n        if (attrs) {\n          child[i] = _clone(parent[i], depth - 1);\n        }\n\n        try {\n          var objProperty = Object.getOwnPropertyDescriptor(parent, i);\n          if (objProperty.set === 'undefined') {\n            // no setter defined. Skip cloning this property\n            continue;\n          }\n          child[i] = _clone(parent[i], depth - 1);\n        } catch(e){\n          if (e instanceof TypeError) {\n            // when in strict mode, TypeError will be thrown if child[i] property only has a getter\n            // we can't do anything about this, other than inform the user that this property cannot be set.\n            continue\n          } else if (e instanceof ReferenceError) {\n            //this may happen in non strict mode\n            continue\n          }\n        }\n\n      }\n\n      if (Object.getOwnPropertySymbols) {\n        var symbols = Object.getOwnPropertySymbols(parent);\n        for (var i = 0; i < symbols.length; i++) {\n          // Don't need to worry about cloning a symbol because it is a primitive,\n          // like a number or string.\n          var symbol = symbols[i];\n          var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);\n          if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {\n            continue;\n          }\n          child[symbol] = _clone(parent[symbol], depth - 1);\n          Object.defineProperty(child, symbol, descriptor);\n        }\n      }\n\n      if (includeNonEnumerable) {\n        var allPropertyNames = Object.getOwnPropertyNames(parent);\n        for (var i = 0; i < allPropertyNames.length; i++) {\n          var propertyName = allPropertyNames[i];\n          var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);\n          if (descriptor && descriptor.enumerable) {\n            continue;\n          }\n          child[propertyName] = _clone(parent[propertyName], depth - 1);\n          Object.defineProperty(child, propertyName, descriptor);\n        }\n      }\n\n      return child;\n    }\n\n    return _clone(parent, depth);\n  }\n\n  /**\n   * Simple flat clone using prototype, accepts only objects, usefull for property\n   * override on FLAT configuration object (no nested props).\n   *\n   * USE WITH CAUTION! This may not behave as you wish if you do not know how this\n   * works.\n   */\n  clone.clonePrototype = function clonePrototype(parent) {\n    if (parent === null)\n      return null;\n\n    var c = function () {};\n    c.prototype = parent;\n    return new c();\n  };\n\n// private utility functions\n\n  function __objToStr(o) {\n    return Object.prototype.toString.call(o);\n  }\n  clone.__objToStr = __objToStr;\n\n  function __isDate(o) {\n    return typeof o === 'object' && __objToStr(o) === '[object Date]';\n  }\n  clone.__isDate = __isDate;\n\n  function __isArray(o) {\n    return typeof o === 'object' && __objToStr(o) === '[object Array]';\n  }\n  clone.__isArray = __isArray;\n\n  function __isRegExp(o) {\n    return typeof o === 'object' && __objToStr(o) === '[object RegExp]';\n  }\n  clone.__isRegExp = __isRegExp;\n\n  function __getRegExpFlags(re) {\n    var flags = '';\n    if (re.global) flags += 'g';\n    if (re.ignoreCase) flags += 'i';\n    if (re.multiline) flags += 'm';\n    return flags;\n  }\n  clone.__getRegExpFlags = __getRegExpFlags;\n\n  return clone;\n})();\n\nexport default clone\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/luch-request/utils.js",
    "content": "'use strict'\n\n// utils is a library of generic helper functions non-specific to axios\n\nconst { toString } = Object.prototype\n\n/**\n * Determine if a value is an Array\n *\n * @param {Object} val The value to test\n * @returns {boolean} True if value is an Array, otherwise false\n */\nexport function isArray(val) {\n    return toString.call(val) === '[object Array]'\n}\n\n/**\n * Determine if a value is an Object\n *\n * @param {Object} val The value to test\n * @returns {boolean} True if value is an Object, otherwise false\n */\nexport function isObject(val) {\n    return val !== null && typeof val === 'object'\n}\n\n/**\n * Determine if a value is a Date\n *\n * @param {Object} val The value to test\n * @returns {boolean} True if value is a Date, otherwise false\n */\nexport function isDate(val) {\n    return toString.call(val) === '[object Date]'\n}\n\n/**\n * Determine if a value is a URLSearchParams object\n *\n * @param {Object} val The value to test\n * @returns {boolean} True if value is a URLSearchParams object, otherwise false\n */\nexport function isURLSearchParams(val) {\n    return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams\n}\n\n/**\n * Iterate over an Array or an Object invoking a function for each item.\n *\n * If `obj` is an Array callback will be called passing\n * the value, index, and complete array for each item.\n *\n * If 'obj' is an Object callback will be called passing\n * the value, key, and complete object for each property.\n *\n * @param {Object|Array} obj The object to iterate\n * @param {Function} fn The callback to invoke for each item\n */\nexport function forEach(obj, fn) {\n    // Don't bother if no value provided\n    if (obj === null || typeof obj === 'undefined') {\n        return\n    }\n\n    // Force an array if not already something iterable\n    if (typeof obj !== 'object') {\n    /* eslint no-param-reassign:0 */\n        obj = [obj]\n    }\n\n    if (isArray(obj)) {\n    // Iterate over array values\n        for (let i = 0, l = obj.length; i < l; i++) {\n            fn.call(null, obj[i], i, obj)\n        }\n    } else {\n    // Iterate over object keys\n        for (const key in obj) {\n            if (Object.prototype.hasOwnProperty.call(obj, key)) {\n                fn.call(null, obj[key], key, obj)\n            }\n        }\n    }\n}\n\n/**\n * 是否为boolean 值\n * @param val\n * @returns {boolean}\n */\nexport function isBoolean(val) {\n    return typeof val === 'boolean'\n}\n\n/**\n * 是否为真正的对象{} new Object\n * @param {any} obj - 检测的对象\n * @returns {boolean}\n */\nexport function isPlainObject(obj) {\n    return Object.prototype.toString.call(obj) === '[object Object]'\n}\n\n/**\n * Function equal to merge with the difference being that no reference\n * to original objects is kept.\n *\n * @see merge\n * @param {Object} obj1 Object to merge\n * @returns {Object} Result of all merge properties\n */\nexport function deepMerge(/* obj1, obj2, obj3, ... */) {\n    const result = {}\n    function assignValue(val, key) {\n        if (typeof result[key] === 'object' && typeof val === 'object') {\n            result[key] = deepMerge(result[key], val)\n        } else if (typeof val === 'object') {\n            result[key] = deepMerge({}, val)\n        } else {\n            result[key] = val\n        }\n    }\n    for (let i = 0, l = arguments.length; i < l; i++) {\n        forEach(arguments[i], assignValue)\n    }\n    return result\n}\n\nexport function isUndefined(val) {\n    return typeof val === 'undefined'\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/mixin/button.js",
    "content": "export default {\n    props: {\n        lang: String,\n        sessionFrom: String,\n        sendMessageTitle: String,\n        sendMessagePath: String,\n        sendMessageImg: String,\n        showMessageCard: Boolean,\n        appParameter: String,\n        formType: String,\n        openType: String\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/mixin/mixin.js",
    "content": "module.exports = {\n    // 定义每个组件都可能需要用到的外部样式以及类名\n    props: {\n        // 每个组件都有的父组件传递的样式，可以为字符串或者对象形式\n        customStyle: {\n            type: [Object, String],\n            default: () => ({})\n        },\n        customClass: {\n            type: String,\n            default: ''\n        },\n        // 跳转的页面路径\n        url: {\n            type: String,\n            default: ''\n        },\n        // 页面跳转的类型\n        linkType: {\n            type: String,\n            default: 'navigateTo'\n        }\n    },\n    data() {\n        return {}\n    },\n    onLoad() {\n        // getRect挂载到$u上，因为这方法需要使用in(this)，所以无法把它独立成一个单独的文件导出\n        this.$u.getRect = this.$uGetRect\n    },\n    created() {\n        // 组件当中，只有created声明周期，为了能在组件使用，故也在created中将方法挂载到$u\n        this.$u.getRect = this.$uGetRect\n    },\n    computed: {\n        // 在2.x版本中，将会把$u挂载到uni对象下，导致在模板中无法使用uni.$u.xxx形式\n        // 所以这里通过computed计算属性将其附加到this.$u上，就可以在模板或者js中使用uni.$u.xxx\n        // 只在nvue环境通过此方式引入完整的$u，其他平台会出现性能问题，非nvue则按需引入（主要原因是props过大）\n        $u() {\n            // #ifndef APP-NVUE\n            // 在非nvue端，移除props，http，mixin等对象，避免在小程序setData时数据过大影响性能\n            return uni.$u.deepMerge(uni.$u, {\n                props: undefined,\n                http: undefined,\n                mixin: undefined\n            })\n            // #endif\n            // #ifdef APP-NVUE\n            return uni.$u\n            // #endif\n        },\n        /**\n         * 生成bem规则类名\n         * 由于微信小程序，H5，nvue之间绑定class的差异，无法通过:class=\"[bem()]\"的形式进行同用\n         * 故采用如下折中做法，最后返回的是数组（一般平台）或字符串（支付宝和字节跳动平台），类似['a', 'b', 'c']或'a b c'的形式\n         * @param {String} name 组件名称\n         * @param {Array} fixed 一直会存在的类名\n         * @param {Array} change 会根据变量值为true或者false而出现或者隐藏的类名\n         * @returns {Array|string}\n         */\n        bem() {\n            return function (name, fixed, change) {\n                // 类名前缀\n                const prefix = `u-${name}--`\n                const classes = {}\n                if (fixed) {\n                    fixed.map((item) => {\n                        // 这里的类名，会一直存在\n                        classes[prefix + this[item]] = true\n                    })\n                }\n                if (change) {\n                    change.map((item) => {\n                        // 这里的类名，会根据this[item]的值为true或者false，而进行添加或者移除某一个类\n                        this[item] ? (classes[prefix + item] = this[item]) : (delete classes[prefix + item])\n                    })\n                }\n                return Object.keys(classes)\n                    // 支付宝，头条小程序无法动态绑定一个数组类名，否则解析出来的结果会带有\",\"，而导致失效\n                    // #ifdef MP-ALIPAY || MP-TOUTIAO || MP-LARK\n                    .join(' ')\n                    // #endif\n            }\n        }\n    },\n    methods: {\n        // 跳转某一个页面\n        openPage(urlKey = 'url') {\n            const url = this[urlKey]\n            if (url) {\n                // 执行类似uni.navigateTo的方法\n                uni[this.linkType]({\n                    url\n                })\n            }\n        },\n        // 查询节点信息\n        // 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸，为支付宝的bug(2020-07-21)\n        // 解决办法为在组件根部再套一个没有任何作用的view元素\n        $uGetRect(selector, all) {\n            return new Promise((resolve) => {\n                uni.createSelectorQuery()\n                    .in(this)[all ? 'selectAll' : 'select'](selector)\n                    .boundingClientRect((rect) => {\n                        if (all && Array.isArray(rect) && rect.length) {\n                            resolve(rect)\n                        }\n                        if (!all && rect) {\n                            resolve(rect)\n                        }\n                    })\n                    .exec()\n            })\n        },\n        getParentData(parentName = '') {\n            // 避免在created中去定义parent变量\n            if (!this.parent) this.parent = {}\n            // 这里的本质原理是，通过获取父组件实例(也即类似u-radio的父组件u-radio-group的this)\n            // 将父组件this中对应的参数，赋值给本组件(u-radio的this)的parentData对象中对应的属性\n            // 之所以需要这么做，是因为所有端中，头条小程序不支持通过this.parent.xxx去监听父组件参数的变化\n            // 此处并不会自动更新子组件的数据，而是依赖父组件u-radio-group去监听data的变化，手动调用更新子组件的方法去重新获取\n            this.parent = uni.$u.$parent.call(this, parentName)\n            if (this.parent.children) {\n                // 如果父组件的children不存在本组件的实例，才将本实例添加到父组件的children中\n                this.parent.children.indexOf(this) === -1 && this.parent.children.push(this)\n            }\n            if (this.parent && this.parentData) {\n                // 历遍parentData中的属性，将parent中的同名属性赋值给parentData\n                Object.keys(this.parentData).map((key) => {\n                    this.parentData[key] = this.parent[key]\n                })\n            }\n        },\n        // 阻止事件冒泡\n        preventEvent(e) {\n            e && typeof (e.stopPropagation) === 'function' && e.stopPropagation()\n        },\n        // 空操作\n        noop(e) {\n            this.preventEvent(e)\n        }\n    },\n    onReachBottom() {\n        uni.$emit('uOnReachBottom')\n    },\n    beforeDestroy() {\n        // 判断当前页面是否存在parent和chldren，一般在checkbox和checkbox-group父子联动的场景会有此情况\n        // 组件销毁时，移除子组件在父组件children数组中的实例，释放资源，避免数据混乱\n        if (this.parent && uni.$u.test.array(this.parent.children)) {\n            // 组件销毁时，移除父组件中的children数组中对应的实例\n            const childrenList = this.parent.children\n            childrenList.map((child, index) => {\n                // 如果相等，则移除\n                if (child === this) {\n                    childrenList.splice(index, 1)\n                }\n            })\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/mixin/mpMixin.js",
    "content": "export default {\n    // #ifdef MP-WEIXIN\n    // 将自定义节点设置成虚拟的，更加接近Vue组件的表现，能更好的使用flex属性\n    options: {\n        virtualHost: true\n    }\n    // #endif\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/mixin/mpShare.js",
    "content": "module.exports = {\n    onLoad() {\n        // 设置默认的转发参数\n        uni.$u.mpShare = {\n            title: '', // 默认为小程序名称\n            path: '', // 默认为当前页面路径\n            imageUrl: '' // 默认为当前页面的截图\n        }\n    },\n    onShareAppMessage() {\n        return uni.$u.mpShare\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/mixin/openType.js",
    "content": "export default {\n    props: {\n        openType: String\n    },\n    methods: {\n        onGetUserInfo(event) {\n            this.$emit('getuserinfo', event.detail)\n        },\n        onContact(event) {\n            this.$emit('contact', event.detail)\n        },\n        onGetPhoneNumber(event) {\n            this.$emit('getphonenumber', event.detail)\n        },\n        onError(event) {\n            this.$emit('error', event.detail)\n        },\n        onLaunchApp(event) {\n            this.$emit('launchapp', event.detail)\n        },\n        onOpenSetting(event) {\n            this.$emit('opensetting', event.detail)\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/mixin/style.js",
    "content": "export default {\n    props: {\n        // flex排列方式\n        flexDirection: {\n            type: String,\n            default: ''\n        },\n        // flex-direction的简写\n        fd: {\n            type: String,\n            default: ''\n        },\n        // 展示类型\n        display: {\n            type: String,\n            default: ''\n        },\n        // display简写\n        d: {\n            type: String,\n            default: ''\n        },\n        // 主轴排列方式\n        justifyContent: {\n            type: String,\n            default: ''\n        },\n        // justifyContent的简写\n        jc: {\n            type: String,\n            default: ''\n        },\n        // 纵轴排列方式\n        alignItems: {\n            type: String,\n            default: ''\n        },\n        // align-items的简写\n        ai: {\n            type: String,\n            default: ''\n        },\n        color: {\n            type: String,\n            default: ''\n        },\n        // color简写\n        c: {\n            type: String,\n            default: ''\n        },\n        // 字体大小\n        fontSize: {\n            type: [String, Number],\n            default: 0\n        },\n        // font-size简写\n        fs: {\n            type: [String, Number],\n            default: ''\n        },\n        margin: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin简写\n        m: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin-top\n        marginTop: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin-top简写\n        mt: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin-right\n        marginRight: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin-right简写\n        mr: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin-bottom\n        marginBottom: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin-bottom简写\n        mb: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin-left\n        marginLeft: {\n            type: [String, Number],\n            default: 0\n        },\n        // margin-left简写\n        ml: {\n            type: [String, Number],\n            default: 0\n        },\n        // padding-left\n        paddingLeft: {\n            type: [String, Number],\n            default: 0\n        },\n        // padding-left简写\n        pl: {\n            type: [String, Number],\n            default: 0\n        },\n        // padding-top\n        paddingTop: {\n            type: [String, Number],\n            default: 0\n        },\n        // padding-top简写\n        pt: {\n            type: [String, Number],\n            default: 0\n        },\n        // padding-right\n        paddingRight: {\n            type: [String, Number],\n            default: 0\n        },\n        // padding-right简写\n        pr: {\n            type: [String, Number],\n            default: 0\n        },\n        // padding-bottom\n        paddingBottom: {\n            type: [String, Number],\n            default: 0\n        },\n        // padding-bottom简写\n        pb: {\n            type: [String, Number],\n            default: 0\n        },\n        // border-radius\n        borderRadius: {\n            type: [String, Number],\n            default: 0\n        },\n        // border-radius简写\n        radius: {\n            type: [String, Number],\n            default: 0\n        },\n        // transform\n        transform: {\n            type: String,\n            default: ''\n        },\n        // 定位\n        position: {\n            type: String,\n            default: ''\n        },\n        // position简写\n        pos: {\n            type: String,\n            default: ''\n        },\n        // 宽度\n        width: {\n            type: [String, Number],\n            default: null\n        },\n        // width简写\n        w: {\n            type: [String, Number],\n            default: null\n        },\n        // 高度\n        height: {\n            type: [String, Number],\n            default: null\n        },\n        // height简写\n        h: {\n            type: [String, Number],\n            default: null\n        },\n        top: {\n            type: [String, Number],\n            default: 0\n        },\n        right: {\n            type: [String, Number],\n            default: 0\n        },\n        bottom: {\n            type: [String, Number],\n            default: 0\n        },\n        left: {\n            type: [String, Number],\n            default: 0\n        }\n    },\n    computed: {\n        viewStyle() {\n            const style = {}\n            const addStyle = uni.$u.addStyle(this.width || this.w) && (style.width = addStyle(this.width || this.w))(this.height || this.h) && (style.height = addStyle(this.height || this.h))(this.margin || this.m) && (style.margin = addStyle(this.margin || this.m))(this.marginTop || this.mt) && (style.marginTop = addStyle(this.marginTop || this.mt))(this.marginRight || this.mr) && (style.marginRight = addStyle(this.marginRight || this.mr))(this.marginBottom || this.mb) && (style.marginBottom = addStyle(this.marginBottom || this.mb))(this.marginLeft || this.ml) && (style.marginLeft = addStyle(this.marginLeft || this.ml))(this.padding || this.p) && (style.padding = addStyle(this.padding || this.p))(this.paddingTop || this.pt) && (style.paddingTop = addStyle(this.paddingTop || this.pt))(this.paddingRight || this.pr) && (style.paddingRight = addStyle(this.paddingRight || this.pr))(this.paddingBottom || this.pb) && (style.paddingBottom = addStyle(this.paddingBottom || this.pb))(this.paddingLeft || this.pl) && (style.paddingLeft = addStyle(this.paddingLeft || this.pl))(this.color || this.c) && (style.color = this.color || this.c)(this.fontSize || this.fs) && (style.fontSize = this.fontSize || this.fs)(this.borderRadius || this.radius) && (style.borderRadius = this.borderRadius || this.radius)(this.position || this.pos) && (this.position = this.position || this.pos)(this.flexDirection || this.fd) && (this.flexDirection = this.flexDirection || this.fd)(this.justifyContent || jc) && (this.justifyContent = this.justifyContent || jc)(this.alignItems || ai) && (this.alignItems = this.alignItems || ai)\n\n            return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))\n        }\n    },\n    methods: {\n        // 获取margin或者padding的单位，比如padding: 0 20转为padding: 0 20px\n        getUnit(unit = '') {\n            // 取出两端空格，分隔成数组，再对数组的每个元素添加单位，最后再合并成字符串\n            return uni.$u.trim(unit).split(' ').map((item) => uni.$u.addUnit(item)).join(' ')\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/mixin/touch.js",
    "content": "const MIN_DISTANCE = 10\n\nfunction getDirection(x, y) {\n    if (x > y && x > MIN_DISTANCE) {\n        return 'horizontal'\n    }\n    if (y > x && y > MIN_DISTANCE) {\n        return 'vertical'\n    }\n    return ''\n}\n\nexport default {\n    methods: {\n        getTouchPoint(e) {\n            if (!e) {\n                return {\n                    x: 0,\n                    y: 0\n                }\n            } if (e.touches && e.touches[0]) {\n                return {\n                    x: e.touches[0].pageX,\n                    y: e.touches[0].pageY\n                }\n            } if (e.changedTouches && e.changedTouches[0]) {\n                return {\n                    x: e.changedTouches[0].pageX,\n                    y: e.changedTouches[0].pageY\n                }\n            }\n            return {\n                x: e.clientX || 0,\n                y: e.clientY || 0\n            }\n        },\n        resetTouchStatus() {\n            this.direction = ''\n            this.deltaX = 0\n            this.deltaY = 0\n            this.offsetX = 0\n            this.offsetY = 0\n        },\n        touchStart(event) {\n            this.resetTouchStatus()\n            const touch = this.getTouchPoint(event)\n            this.startX = touch.x\n            this.startY = touch.y\n        },\n        touchMove(event) {\n            const touch = this.getTouchPoint(event)\n            this.deltaX = touch.x - this.startX\n            this.deltaY = touch.y - this.startY\n            this.offsetX = Math.abs(this.deltaX)\n            this.offsetY = Math.abs(this.deltaY)\n            this.direction =\t\t\t\tthis.direction || getDirection(this.offsetX, this.offsetY)\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/util/async-validator.js",
    "content": "function _extends() {\n    _extends = Object.assign || function (target) {\n        for (let i = 1; i < arguments.length; i++) {\n            const source = arguments[i]\n\n            for (const key in source) {\n                if (Object.prototype.hasOwnProperty.call(source, key)) {\n                    target[key] = source[key]\n                }\n            }\n        }\n\n        return target\n    }\n\n    return _extends.apply(this, arguments)\n}\n\n/* eslint no-console:0 */\nconst formatRegExp = /%[sdj%]/g\nlet warning = function warning() {} // don't print warning message when in production env or node runtime\n\nif (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production' && typeof window\n\t!== 'undefined' && typeof document !== 'undefined') {\n    warning = function warning(type, errors) {\n        if (typeof console !== 'undefined' && console.warn) {\n            if (errors.every((e) => typeof e === 'string')) {\n                console.warn(type, errors)\n            }\n        }\n    }\n}\n\nfunction convertFieldsError(errors) {\n    if (!errors || !errors.length) return null\n    const fields = {}\n    errors.forEach((error) => {\n        const { field } = error\n        fields[field] = fields[field] || []\n        fields[field].push(error)\n    })\n    return fields\n}\n\nfunction format() {\n    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\n        args[_key] = arguments[_key]\n    }\n\n    let i = 1\n    const f = args[0]\n    const len = args.length\n\n    if (typeof f === 'function') {\n        return f.apply(null, args.slice(1))\n    }\n\n    if (typeof f === 'string') {\n        let str = String(f).replace(formatRegExp, (x) => {\n            if (x === '%%') {\n                return '%'\n            }\n\n            if (i >= len) {\n                return x\n            }\n\n            switch (x) {\n            case '%s':\n                return String(args[i++])\n\n            case '%d':\n                return Number(args[i++])\n\n            case '%j':\n                try {\n                    return JSON.stringify(args[i++])\n                } catch (_) {\n                    return '[Circular]'\n                }\n\n                break\n\n            default:\n                return x\n            }\n        })\n\n        for (let arg = args[i]; i < len; arg = args[++i]) {\n            str += ` ${arg}`\n        }\n\n        return str\n    }\n\n    return f\n}\n\nfunction isNativeStringType(type) {\n    return type === 'string' || type === 'url' || type === 'hex' || type === 'email' || type === 'pattern'\n}\n\nfunction isEmptyValue(value, type) {\n    if (value === undefined || value === null) {\n        return true\n    }\n\n    if (type === 'array' && Array.isArray(value) && !value.length) {\n        return true\n    }\n\n    if (isNativeStringType(type) && typeof value === 'string' && !value) {\n        return true\n    }\n\n    return false\n}\n\nfunction asyncParallelArray(arr, func, callback) {\n    const results = []\n    let total = 0\n    const arrLength = arr.length\n\n    function count(errors) {\n        results.push.apply(results, errors)\n        total++\n\n        if (total === arrLength) {\n            callback(results)\n        }\n    }\n\n    arr.forEach((a) => {\n        func(a, count)\n    })\n}\n\nfunction asyncSerialArray(arr, func, callback) {\n    let index = 0\n    const arrLength = arr.length\n\n    function next(errors) {\n        if (errors && errors.length) {\n            callback(errors)\n            return\n        }\n\n        const original = index\n        index += 1\n\n        if (original < arrLength) {\n            func(arr[original], next)\n        } else {\n            callback([])\n        }\n    }\n\n    next([])\n}\n\nfunction flattenObjArr(objArr) {\n    const ret = []\n    Object.keys(objArr).forEach((k) => {\n        ret.push.apply(ret, objArr[k])\n    })\n    return ret\n}\n\nfunction asyncMap(objArr, option, func, callback) {\n    if (option.first) {\n        const _pending = new Promise((resolve, reject) => {\n            const next = function next(errors) {\n                callback(errors)\n                return errors.length ? reject({\n                    errors,\n                    fields: convertFieldsError(errors)\n                }) : resolve()\n            }\n\n            const flattenArr = flattenObjArr(objArr)\n            asyncSerialArray(flattenArr, func, next)\n        })\n\n        _pending.catch((e) => e)\n\n        return _pending\n    }\n\n    let firstFields = option.firstFields || []\n\n    if (firstFields === true) {\n        firstFields = Object.keys(objArr)\n    }\n\n    const objArrKeys = Object.keys(objArr)\n    const objArrLength = objArrKeys.length\n    let total = 0\n    const results = []\n    const pending = new Promise((resolve, reject) => {\n        const next = function next(errors) {\n            results.push.apply(results, errors)\n            total++\n\n            if (total === objArrLength) {\n                callback(results)\n                return results.length ? reject({\n                    errors: results,\n                    fields: convertFieldsError(results)\n                }) : resolve()\n            }\n        }\n\n        if (!objArrKeys.length) {\n            callback(results)\n            resolve()\n        }\n\n        objArrKeys.forEach((key) => {\n            const arr = objArr[key]\n\n            if (firstFields.indexOf(key) !== -1) {\n                asyncSerialArray(arr, func, next)\n            } else {\n                asyncParallelArray(arr, func, next)\n            }\n        })\n    })\n    pending.catch((e) => e)\n    return pending\n}\n\nfunction complementError(rule) {\n    return function (oe) {\n        if (oe && oe.message) {\n            oe.field = oe.field || rule.fullField\n            return oe\n        }\n\n        return {\n            message: typeof oe === 'function' ? oe() : oe,\n            field: oe.field || rule.fullField\n        }\n    }\n}\n\nfunction deepMerge(target, source) {\n    if (source) {\n        for (const s in source) {\n            if (source.hasOwnProperty(s)) {\n                const value = source[s]\n\n                if (typeof value === 'object' && typeof target[s] === 'object') {\n                    target[s] = { ...target[s], ...value }\n                } else {\n                    target[s] = value\n                }\n            }\n        }\n    }\n\n    return target\n}\n\n/**\n *  Rule for validating required fields.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param source The source object being validated.\n *  @param errors An array of errors that this rule may add\n *  validation errors to.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction required(rule, value, source, errors, options, type) {\n    if (rule.required && (!source.hasOwnProperty(rule.field) || isEmptyValue(value, type || rule.type))) {\n        errors.push(format(options.messages.required, rule.fullField))\n    }\n}\n\n/**\n *  Rule for validating whitespace.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param source The source object being validated.\n *  @param errors An array of errors that this rule may add\n *  validation errors to.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction whitespace(rule, value, source, errors, options) {\n    if (/^\\s+$/.test(value) || value === '') {\n        errors.push(format(options.messages.whitespace, rule.fullField))\n    }\n}\n\n/* eslint max-len:0 */\n\nconst pattern = {\n    // http://emailregex.com/\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,}))$/,\n    url: new RegExp(\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]*)?$',\n        'i'\n    ),\n    hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i\n}\nvar types = {\n    integer: function integer(value) {\n        return /^(-)?\\d+$/.test(value);\n    },\n    float: function float(value) {\n        return /^(-)?\\d+(\\.\\d+)?$/.test(value);\n    },\n    array: function array(value) {\n        return Array.isArray(value)\n    },\n    regexp: function regexp(value) {\n        if (value instanceof RegExp) {\n            return true\n        }\n\n        try {\n            return !!new RegExp(value)\n        } catch (e) {\n            return false\n        }\n    },\n    date: function date(value) {\n        return typeof value.getTime === 'function' && typeof value.getMonth === 'function' && typeof value.getYear\n\t\t\t=== 'function'\n    },\n    number: function number(value) {\n        if (isNaN(value)) {\n            return false\n        }\n\n        // 修改源码，将字符串数值先转为数值\n        return typeof +value === 'number'\n    },\n    object: function object(value) {\n        return typeof value === 'object' && !types.array(value)\n    },\n    method: function method(value) {\n        return typeof value === 'function'\n    },\n    email: function email(value) {\n        return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255\n    },\n    url: function url(value) {\n        return typeof value === 'string' && !!value.match(pattern.url)\n    },\n    hex: function hex(value) {\n        return typeof value === 'string' && !!value.match(pattern.hex)\n    }\n}\n/**\n *  Rule for validating the type of a value.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param source The source object being validated.\n *  @param errors An array of errors that this rule may add\n *  validation errors to.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction type(rule, value, source, errors, options) {\n    if (rule.required && value === undefined) {\n        required(rule, value, source, errors, options)\n        return\n    }\n\n    const custom = ['integer', 'float', 'array', 'regexp', 'object', 'method', 'email', 'number', 'date', 'url', 'hex']\n    const ruleType = rule.type\n\n    if (custom.indexOf(ruleType) > -1) {\n        if (!types[ruleType](value)) {\n            errors.push(format(options.messages.types[ruleType], rule.fullField, rule.type))\n        } // straight typeof check\n    } else if (ruleType && typeof value !== rule.type) {\n        errors.push(format(options.messages.types[ruleType], rule.fullField, rule.type))\n    }\n}\n\n/**\n *  Rule for validating minimum and maximum allowed values.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param source The source object being validated.\n *  @param errors An array of errors that this rule may add\n *  validation errors to.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction range(rule, value, source, errors, options) {\n    const len = typeof rule.len === 'number'\n    const min = typeof rule.min === 'number'\n    const max = typeof rule.max === 'number' // 正则匹配码点范围从U+010000一直到U+10FFFF的文字（补充平面Supplementary Plane）\n\n    const spRegexp = /[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g\n    let val = value\n    let key = null\n    const num = typeof value === 'number'\n    const str = typeof value === 'string'\n    const arr = Array.isArray(value)\n\n    if (num) {\n        key = 'number'\n    } else if (str) {\n        key = 'string'\n    } else if (arr) {\n        key = 'array'\n    } // if the value is not of a supported type for range validation\n    // the validation rule rule should use the\n    // type property to also test for a particular type\n\n    if (!key) {\n        return false\n    }\n\n    if (arr) {\n        val = value.length\n    }\n\n    if (str) {\n        // 处理码点大于U+010000的文字length属性不准确的bug，如\"𠮷𠮷𠮷\".lenght !== 3\n        val = value.replace(spRegexp, '_').length\n    }\n\n    if (len) {\n        if (val !== rule.len) {\n            errors.push(format(options.messages[key].len, rule.fullField, rule.len))\n        }\n    } else if (min && !max && val < rule.min) {\n        errors.push(format(options.messages[key].min, rule.fullField, rule.min))\n    } else if (max && !min && val > rule.max) {\n        errors.push(format(options.messages[key].max, rule.fullField, rule.max))\n    } else if (min && max && (val < rule.min || val > rule.max)) {\n        errors.push(format(options.messages[key].range, rule.fullField, rule.min, rule.max))\n    }\n}\n\nconst ENUM = 'enum'\n/**\n *  Rule for validating a value exists in an enumerable list.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param source The source object being validated.\n *  @param errors An array of errors that this rule may add\n *  validation errors to.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction enumerable(rule, value, source, errors, options) {\n    rule[ENUM] = Array.isArray(rule[ENUM]) ? rule[ENUM] : []\n\n    if (rule[ENUM].indexOf(value) === -1) {\n        errors.push(format(options.messages[ENUM], rule.fullField, rule[ENUM].join(', ')))\n    }\n}\n\n/**\n *  Rule for validating a regular expression pattern.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param source The source object being validated.\n *  @param errors An array of errors that this rule may add\n *  validation errors to.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction pattern$1(rule, value, source, errors, options) {\n    if (rule.pattern) {\n        if (rule.pattern instanceof RegExp) {\n            // if a RegExp instance is passed, reset `lastIndex` in case its `global`\n            // flag is accidentally set to `true`, which in a validation scenario\n            // is not necessary and the result might be misleading\n            rule.pattern.lastIndex = 0\n\n            if (!rule.pattern.test(value)) {\n                errors.push(format(options.messages.pattern.mismatch, rule.fullField, value, rule.pattern))\n            }\n        } else if (typeof rule.pattern === 'string') {\n            const _pattern = new RegExp(rule.pattern)\n\n            if (!_pattern.test(value)) {\n                errors.push(format(options.messages.pattern.mismatch, rule.fullField, value, rule.pattern))\n            }\n        }\n    }\n}\n\nconst rules = {\n    required,\n    whitespace,\n    type,\n    range,\n    enum: enumerable,\n    pattern: pattern$1\n}\n\n/**\n *  Performs validation for string types.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction string(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value, 'string') && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options, 'string')\n\n        if (!isEmptyValue(value, 'string')) {\n            rules.type(rule, value, source, errors, options)\n            rules.range(rule, value, source, errors, options)\n            rules.pattern(rule, value, source, errors, options)\n\n            if (rule.whitespace === true) {\n                rules.whitespace(rule, value, source, errors, options)\n            }\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates a function.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction method(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (value !== undefined) {\n            rules.type(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates a number.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction number(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (value === '') {\n            value = undefined\n        }\n\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (value !== undefined) {\n            rules.type(rule, value, source, errors, options)\n            rules.range(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates a boolean.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction _boolean(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (value !== undefined) {\n            rules.type(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates the regular expression type.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction regexp(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (!isEmptyValue(value)) {\n            rules.type(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates a number is an integer.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction integer(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (value !== undefined) {\n            rules.type(rule, value, source, errors, options)\n            rules.range(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates a number is a floating point number.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction floatFn(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (value !== undefined) {\n            rules.type(rule, value, source, errors, options)\n            rules.range(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates an array.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction array(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value, 'array') && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options, 'array')\n\n        if (!isEmptyValue(value, 'array')) {\n            rules.type(rule, value, source, errors, options)\n            rules.range(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates an object.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction object(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (value !== undefined) {\n            rules.type(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\nconst ENUM$1 = 'enum'\n/**\n *  Validates an enumerable list.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction enumerable$1(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (value !== undefined) {\n            rules[ENUM$1](rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Validates a regular expression pattern.\n *\n *  Performs validation when a rule only contains\n *  a pattern property but is not declared as a string type.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction pattern$2(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value, 'string') && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (!isEmptyValue(value, 'string')) {\n            rules.pattern(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\nfunction date(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n\n        if (!isEmptyValue(value)) {\n            let dateObject\n\n            if (typeof value === 'number') {\n                dateObject = new Date(value)\n            } else {\n                dateObject = value\n            }\n\n            rules.type(rule, dateObject, source, errors, options)\n\n            if (dateObject) {\n                rules.range(rule, dateObject.getTime(), source, errors, options)\n            }\n        }\n    }\n\n    callback(errors)\n}\n\nfunction required$1(rule, value, callback, source, options) {\n    const errors = []\n    const type = Array.isArray(value) ? 'array' : typeof value\n    rules.required(rule, value, source, errors, options, type)\n    callback(errors)\n}\n\nfunction type$1(rule, value, callback, source, options) {\n    const ruleType = rule.type\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value, ruleType) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options, ruleType)\n\n        if (!isEmptyValue(value, ruleType)) {\n            rules.type(rule, value, source, errors, options)\n        }\n    }\n\n    callback(errors)\n}\n\n/**\n *  Performs validation for any type.\n *\n *  @param rule The validation rule.\n *  @param value The value of the field on the source object.\n *  @param callback The callback function.\n *  @param source The source object being validated.\n *  @param options The validation options.\n *  @param options.messages The validation messages.\n */\n\nfunction any(rule, value, callback, source, options) {\n    const errors = []\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\n\n    if (validate) {\n        if (isEmptyValue(value) && !rule.required) {\n            return callback()\n        }\n\n        rules.required(rule, value, source, errors, options)\n    }\n\n    callback(errors)\n}\n\nconst validators = {\n    string,\n    method,\n    number,\n    boolean: _boolean,\n    regexp,\n    integer,\n    float: floatFn,\n    array,\n    object,\n    enum: enumerable$1,\n    pattern: pattern$2,\n    date,\n    url: type$1,\n    hex: type$1,\n    email: type$1,\n    required: required$1,\n    any\n}\n\nfunction newMessages() {\n    return {\n        default: 'Validation error on field %s',\n        required: '%s is required',\n        enum: '%s must be one of %s',\n        whitespace: '%s cannot be empty',\n        date: {\n            format: '%s date %s is invalid for format %s',\n            parse: '%s date could not be parsed, %s is invalid ',\n            invalid: '%s date %s is invalid'\n        },\n        types: {\n            string: '%s is not a %s',\n            method: '%s is not a %s (function)',\n            array: '%s is not an %s',\n            object: '%s is not an %s',\n            number: '%s is not a %s',\n            date: '%s is not a %s',\n            boolean: '%s is not a %s',\n            integer: '%s is not an %s',\n            float: '%s is not a %s',\n            regexp: '%s is not a valid %s',\n            email: '%s is not a valid %s',\n            url: '%s is not a valid %s',\n            hex: '%s is not a valid %s'\n        },\n        string: {\n            len: '%s must be exactly %s characters',\n            min: '%s must be at least %s characters',\n            max: '%s cannot be longer than %s characters',\n            range: '%s must be between %s and %s characters'\n        },\n        number: {\n            len: '%s must equal %s',\n            min: '%s cannot be less than %s',\n            max: '%s cannot be greater than %s',\n            range: '%s must be between %s and %s'\n        },\n        array: {\n            len: '%s must be exactly %s in length',\n            min: '%s cannot be less than %s in length',\n            max: '%s cannot be greater than %s in length',\n            range: '%s must be between %s and %s in length'\n        },\n        pattern: {\n            mismatch: '%s value %s does not match pattern %s'\n        },\n        clone: function clone() {\n            const cloned = JSON.parse(JSON.stringify(this))\n            cloned.clone = this.clone\n            return cloned\n        }\n    }\n}\nconst messages = newMessages()\n\n/**\n *  Encapsulates a validation schema.\n *\n *  @param descriptor An object declaring validation rules\n *  for this schema.\n */\n\nfunction Schema(descriptor) {\n    this.rules = null\n    this._messages = messages\n    this.define(descriptor)\n}\n\nSchema.prototype = {\n    messages: function messages(_messages) {\n        if (_messages) {\n            this._messages = deepMerge(newMessages(), _messages)\n        }\n\n        return this._messages\n    },\n    define: function define(rules) {\n        if (!rules) {\n            throw new Error('Cannot configure a schema with no rules')\n        }\n\n        if (typeof rules !== 'object' || Array.isArray(rules)) {\n            throw new Error('Rules must be an object')\n        }\n\n        this.rules = {}\n        let z\n        let item\n\n        for (z in rules) {\n            if (rules.hasOwnProperty(z)) {\n                item = rules[z]\n                this.rules[z] = Array.isArray(item) ? item : [item]\n            }\n        }\n    },\n    validate: function validate(source_, o, oc) {\n        const _this = this\n\n        if (o === void 0) {\n            o = {}\n        }\n\n        if (oc === void 0) {\n            oc = function oc() {}\n        }\n\n        let source = source_\n        let options = o\n        let callback = oc\n\n        if (typeof options === 'function') {\n            callback = options\n            options = {}\n        }\n\n        if (!this.rules || Object.keys(this.rules).length === 0) {\n            if (callback) {\n                callback()\n            }\n\n            return Promise.resolve()\n        }\n\n        function complete(results) {\n            let i\n            let errors = []\n            let fields = {}\n\n            function add(e) {\n                if (Array.isArray(e)) {\n                    let _errors\n\n                    errors = (_errors = errors).concat.apply(_errors, e)\n                } else {\n                    errors.push(e)\n                }\n            }\n\n            for (i = 0; i < results.length; i++) {\n                add(results[i])\n            }\n\n            if (!errors.length) {\n                errors = null\n                fields = null\n            } else {\n                fields = convertFieldsError(errors)\n            }\n\n            callback(errors, fields)\n        }\n\n        if (options.messages) {\n            let messages$1 = this.messages()\n\n            if (messages$1 === messages) {\n                messages$1 = newMessages()\n            }\n\n            deepMerge(messages$1, options.messages)\n            options.messages = messages$1\n        } else {\n            options.messages = this.messages()\n        }\n\n        let arr\n        let value\n        const series = {}\n        const keys = options.keys || Object.keys(this.rules)\n        keys.forEach((z) => {\n            arr = _this.rules[z]\n            value = source[z]\n            arr.forEach((r) => {\n                let rule = r\n\n                if (typeof rule.transform === 'function') {\n                    if (source === source_) {\n                        source = { ...source }\n                    }\n\n                    value = source[z] = rule.transform(value)\n                }\n\n                if (typeof rule === 'function') {\n                    rule = {\n                        validator: rule\n                    }\n                } else {\n                    rule = { ...rule }\n                }\n\n                rule.validator = _this.getValidationMethod(rule)\n                rule.field = z\n                rule.fullField = rule.fullField || z\n                rule.type = _this.getType(rule)\n\n                if (!rule.validator) {\n                    return\n                }\n\n                series[z] = series[z] || []\n                series[z].push({\n                    rule,\n                    value,\n                    source,\n                    field: z\n                })\n            })\n        })\n        const errorFields = {}\n        return asyncMap(series, options, (data, doIt) => {\n            const { rule } = data\n            let deep = (rule.type === 'object' || rule.type === 'array') && (typeof rule.fields === 'object' || typeof rule.defaultField\n\t\t\t\t=== 'object')\n            deep = deep && (rule.required || !rule.required && data.value)\n            rule.field = data.field\n\n            function addFullfield(key, schema) {\n                return { ...schema, fullField: `${rule.fullField}.${key}` }\n            }\n\n            function cb(e) {\n                if (e === void 0) {\n                    e = []\n                }\n\n                let errors = e\n\n                if (!Array.isArray(errors)) {\n                    errors = [errors]\n                }\n\n                if (!options.suppressWarning && errors.length) {\n                    Schema.warning('async-validator:', errors)\n                }\n\n                if (errors.length && rule.message) {\n                    errors = [].concat(rule.message)\n                }\n\n                errors = errors.map(complementError(rule))\n\n                if (options.first && errors.length) {\n                    errorFields[rule.field] = 1\n                    return doIt(errors)\n                }\n\n                if (!deep) {\n                    doIt(errors)\n                } else {\n                    // if rule is required but the target object\n                    // does not exist fail at the rule level and don't\n                    // go deeper\n                    if (rule.required && !data.value) {\n                        if (rule.message) {\n                            errors = [].concat(rule.message).map(complementError(rule))\n                        } else if (options.error) {\n                            errors = [options.error(rule, format(options.messages.required, rule.field))]\n                        } else {\n                            errors = []\n                        }\n\n                        return doIt(errors)\n                    }\n\n                    let fieldsSchema = {}\n\n                    if (rule.defaultField) {\n                        for (const k in data.value) {\n                            if (data.value.hasOwnProperty(k)) {\n                                fieldsSchema[k] = rule.defaultField\n                            }\n                        }\n                    }\n\n                    fieldsSchema = { ...fieldsSchema, ...data.rule.fields }\n\n                    for (const f in fieldsSchema) {\n                        if (fieldsSchema.hasOwnProperty(f)) {\n                            const fieldSchema = Array.isArray(fieldsSchema[f]) ? fieldsSchema[f] : [fieldsSchema[f]]\n                            fieldsSchema[f] = fieldSchema.map(addFullfield.bind(null, f))\n                        }\n                    }\n\n                    const schema = new Schema(fieldsSchema)\n                    schema.messages(options.messages)\n\n                    if (data.rule.options) {\n                        data.rule.options.messages = options.messages\n                        data.rule.options.error = options.error\n                    }\n\n                    schema.validate(data.value, data.rule.options || options, (errs) => {\n                        const finalErrors = []\n\n                        if (errors && errors.length) {\n                            finalErrors.push.apply(finalErrors, errors)\n                        }\n\n                        if (errs && errs.length) {\n                            finalErrors.push.apply(finalErrors, errs)\n                        }\n\n                        doIt(finalErrors.length ? finalErrors : null)\n                    })\n                }\n            }\n\n            let res\n\n            if (rule.asyncValidator) {\n                res = rule.asyncValidator(rule, data.value, cb, data.source, options)\n            } else if (rule.validator) {\n                res = rule.validator(rule, data.value, cb, data.source, options)\n\n                if (res === true) {\n                    cb()\n                } else if (res === false) {\n                    cb(rule.message || `${rule.field} fails`)\n                } else if (res instanceof Array) {\n                    cb(res)\n                } else if (res instanceof Error) {\n                    cb(res.message)\n                }\n            }\n\n            if (res && res.then) {\n                res.then(() => cb(), (e) => cb(e))\n            }\n        }, (results) => {\n            complete(results)\n        })\n    },\n    getType: function getType(rule) {\n        if (rule.type === undefined && rule.pattern instanceof RegExp) {\n            rule.type = 'pattern'\n        }\n\n        if (typeof rule.validator !== 'function' && rule.type && !validators.hasOwnProperty(rule.type)) {\n            throw new Error(format('Unknown rule type %s', rule.type))\n        }\n\n        return rule.type || 'string'\n    },\n    getValidationMethod: function getValidationMethod(rule) {\n        if (typeof rule.validator === 'function') {\n            return rule.validator\n        }\n\n        const keys = Object.keys(rule)\n        const messageIndex = keys.indexOf('message')\n\n        if (messageIndex !== -1) {\n            keys.splice(messageIndex, 1)\n        }\n\n        if (keys.length === 1 && keys[0] === 'required') {\n            return validators.required\n        }\n\n        return validators[this.getType(rule)] || false\n    }\n}\n\nSchema.register = function register(type, validator) {\n    if (typeof validator !== 'function') {\n        throw new Error('Cannot register a validator by type, validator is not a function')\n    }\n\n    validators[type] = validator\n}\n\nSchema.warning = warning\nSchema.messages = messages\n\nexport default Schema\n// # sourceMappingURL=index.js.map\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/util/calendar.js",
    "content": "/**\n* @1900-2100区间内的公历、农历互转\n* @charset UTF-8\n* @github  https://github.com/jjonline/calendar.js\n* @Author  Jea杨(JJonline@JJonline.Cn)\n* @Time    2014-7-21\n* @Time    2016-8-13 Fixed 2033hex、Attribution Annals\n* @Time    2016-9-25 Fixed lunar LeapMonth Param Bug\n* @Time    2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year\n* @Version 1.0.3\n* @公历转农历：calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]\n* @农历转公历：calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]\n*/\n/* eslint-disable */\nvar calendar = {\n\n    /**\n        * 农历1900-2100的润大小信息表\n        * @Array Of Property\n        * @return Hex\n        */\n    lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909\n        0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919\n        0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929\n        0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939\n        0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949\n        0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959\n        0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969\n        0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979\n        0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989\n        0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999\n        0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009\n        0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019\n        0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029\n        0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039\n        0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049\n        /** Add By JJonline@JJonline.Cn**/\n        0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059\n        0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069\n        0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079\n        0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089\n        0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099\n        0x0d520], // 2100\n\n    /**\n        * 公历每个月份的天数普通表\n        * @Array Of Property\n        * @return Number\n        */\n    solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],\n\n    /**\n        * 天干地支之天干速查表\n        * @Array Of Property trans[\"甲\",\"乙\",\"丙\",\"丁\",\"戊\",\"己\",\"庚\",\"辛\",\"壬\",\"癸\"]\n        * @return Cn string\n        */\n    Gan: ['\\u7532', '\\u4e59', '\\u4e19', '\\u4e01', '\\u620a', '\\u5df1', '\\u5e9a', '\\u8f9b', '\\u58ec', '\\u7678'],\n\n    /**\n        * 天干地支之地支速查表\n        * @Array Of Property\n        * @trans[\"子\",\"丑\",\"寅\",\"卯\",\"辰\",\"巳\",\"午\",\"未\",\"申\",\"酉\",\"戌\",\"亥\"]\n        * @return Cn string\n        */\n    Zhi: ['\\u5b50', '\\u4e11', '\\u5bc5', '\\u536f', '\\u8fb0', '\\u5df3', '\\u5348', '\\u672a', '\\u7533', '\\u9149', '\\u620c', '\\u4ea5'],\n\n    /**\n        * 天干地支之地支速查表<=>生肖\n        * @Array Of Property\n        * @trans[\"鼠\",\"牛\",\"虎\",\"兔\",\"龙\",\"蛇\",\"马\",\"羊\",\"猴\",\"鸡\",\"狗\",\"猪\"]\n        * @return Cn string\n        */\n    Animals: ['\\u9f20', '\\u725b', '\\u864e', '\\u5154', '\\u9f99', '\\u86c7', '\\u9a6c', '\\u7f8a', '\\u7334', '\\u9e21', '\\u72d7', '\\u732a'],\n\n    /**\n        * 24节气速查表\n        * @Array Of Property\n        * @trans[\"小寒\",\"大寒\",\"立春\",\"雨水\",\"惊蛰\",\"春分\",\"清明\",\"谷雨\",\"立夏\",\"小满\",\"芒种\",\"夏至\",\"小暑\",\"大暑\",\"立秋\",\"处暑\",\"白露\",\"秋分\",\"寒露\",\"霜降\",\"立冬\",\"小雪\",\"大雪\",\"冬至\"]\n        * @return Cn string\n        */\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'],\n\n    /**\n        * 1900-2100各年的24节气日期速查表\n        * @Array Of Property\n        * @return 0x string For splice\n        */\n    sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',\n        '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\n        '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',\n        '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',\n        'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',\n        '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',\n        '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',\n        '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',\n        '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',\n        '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\n        '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',\n        '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',\n        '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\n        '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\n        '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',\n        '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',\n        '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',\n        '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',\n        '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',\n        '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\n        '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\n        '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\n        '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',\n        '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\n        '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\n        '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\n        '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',\n        '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',\n        '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',\n        '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',\n        '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',\n        '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\n        '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\n        '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\n        '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',\n        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\n        '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\n        '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\n        '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',\n        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',\n        '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',\n        '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\n        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',\n        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\n        '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\n        '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\n        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',\n        '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\n        '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\n        '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',\n        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',\n        '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',\n        '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\n        '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',\n        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',\n        '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\n        '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',\n        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',\n        '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',\n        '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\n        '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',\n        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',\n        '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',\n        '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\n        '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',\n        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',\n        '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],\n\n    /**\n        * 数字转中文速查表\n        * @Array Of Property\n        * @trans ['日','一','二','三','四','五','六','七','八','九','十']\n        * @return Cn string\n        */\n    nStr1: ['\\u65e5', '\\u4e00', '\\u4e8c', '\\u4e09', '\\u56db', '\\u4e94', '\\u516d', '\\u4e03', '\\u516b', '\\u4e5d', '\\u5341'],\n\n    /**\n        * 日期转农历称呼速查表\n        * @Array Of Property\n        * @trans ['初','十','廿','卅']\n        * @return Cn string\n        */\n    nStr2: ['\\u521d', '\\u5341', '\\u5eff', '\\u5345'],\n\n    /**\n        * 月份转农历称呼速查表\n        * @Array Of Property\n        * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']\n        * @return Cn string\n        */\n    nStr3: ['\\u6b63', '\\u4e8c', '\\u4e09', '\\u56db', '\\u4e94', '\\u516d', '\\u4e03', '\\u516b', '\\u4e5d', '\\u5341', '\\u51ac', '\\u814a'],\n\n    /**\n        * 返回农历y年一整年的总天数\n        * @param lunar Year\n        * @return Number\n        * @eg:var count = calendar.lYearDays(1987) ;//count=387\n        */\n    lYearDays: function (y) {\n        var i; var sum = 348\n        for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }\n        return (sum + this.leapDays(y))\n    },\n\n    /**\n        * 返回农历y年闰月是哪个月；若y年没有闰月 则返回0\n        * @param lunar Year\n        * @return Number (0-12)\n        * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6\n        */\n    leapMonth: function (y) { // 闰字编码 \\u95f0\n        return (this.lunarInfo[y - 1900] & 0xf)\n    },\n\n    /**\n        * 返回农历y年闰月的天数 若该年没有闰月则返回0\n        * @param lunar Year\n        * @return Number (0、29、30)\n        * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29\n        */\n    leapDays: function (y) {\n        if (this.leapMonth(y)) {\n            return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)\n        }\n        return (0)\n    },\n\n    /**\n        * 返回农历y年m月（非闰月）的总天数，计算m为闰月时的天数请使用leapDays方法\n        * @param lunar Year\n        * @return Number (-1、29、30)\n        * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29\n        */\n    monthDays: function (y, m) {\n        if (m > 12 || m < 1) { return -1 }// 月份参数从1至12，参数错误返回-1\n        return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)\n    },\n\n    /**\n        * 返回公历(!)y年m月的天数\n        * @param solar Year\n        * @return Number (-1、28、29、30、31)\n        * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30\n        */\n    solarDays: function (y, m) {\n        if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1\n        var ms = m - 1\n        if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29\n            return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)\n        } else {\n            return (this.solarMonth[ms])\n        }\n    },\n\n    /**\n       * 农历年份转换为干支纪年\n       * @param  lYear 农历年的年份数\n       * @return Cn string\n       */\n    toGanZhiYear: function (lYear) {\n        var ganKey = (lYear - 3) % 10\n        var zhiKey = (lYear - 3) % 12\n        if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干\n        if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支\n        return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]\n    },\n\n    /**\n       * 公历月、日判断所属星座\n       * @param  cMonth [description]\n       * @param  cDay [description]\n       * @return Cn string\n       */\n    toAstro: function (cMonth, cDay) {\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'\n        var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]\n        return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\\u5ea7'// 座\n    },\n\n    /**\n        * 传入offset偏移量返回干支\n        * @param offset 相对甲子的偏移量\n        * @return Cn string\n        */\n    toGanZhi: function (offset) {\n        return this.Gan[offset % 10] + this.Zhi[offset % 12]\n    },\n\n    /**\n        * 传入公历(!)y年获得该年第n个节气的公历日期\n        * @param y公历年(1900-2100)；n二十四节气中的第几个节气(1~24)；从n=1(小寒)算起\n        * @return day Number\n        * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春\n        */\n    getTerm: function (y, n) {\n        if (y < 1900 || y > 2100) { return -1 }\n        if (n < 1 || n > 24) { return -1 }\n        var _table = this.sTermInfo[y - 1900]\n        var _info = [\n            parseInt('0x' + _table.substr(0, 5)).toString(),\n            parseInt('0x' + _table.substr(5, 5)).toString(),\n            parseInt('0x' + _table.substr(10, 5)).toString(),\n            parseInt('0x' + _table.substr(15, 5)).toString(),\n            parseInt('0x' + _table.substr(20, 5)).toString(),\n            parseInt('0x' + _table.substr(25, 5)).toString()\n        ]\n        var _calday = [\n            _info[0].substr(0, 1),\n            _info[0].substr(1, 2),\n            _info[0].substr(3, 1),\n            _info[0].substr(4, 2),\n\n            _info[1].substr(0, 1),\n            _info[1].substr(1, 2),\n            _info[1].substr(3, 1),\n            _info[1].substr(4, 2),\n\n            _info[2].substr(0, 1),\n            _info[2].substr(1, 2),\n            _info[2].substr(3, 1),\n            _info[2].substr(4, 2),\n\n            _info[3].substr(0, 1),\n            _info[3].substr(1, 2),\n            _info[3].substr(3, 1),\n            _info[3].substr(4, 2),\n\n            _info[4].substr(0, 1),\n            _info[4].substr(1, 2),\n            _info[4].substr(3, 1),\n            _info[4].substr(4, 2),\n\n            _info[5].substr(0, 1),\n            _info[5].substr(1, 2),\n            _info[5].substr(3, 1),\n            _info[5].substr(4, 2)\n        ]\n        return parseInt(_calday[n - 1])\n    },\n\n    /**\n        * 传入农历数字月份返回汉语通俗表示法\n        * @param lunar month\n        * @return Cn string\n        * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'\n        */\n    toChinaMonth: function (m) { // 月 => \\u6708\n        if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1\n        var s = this.nStr3[m - 1]\n        s += '\\u6708'// 加上月字\n        return s\n    },\n\n    /**\n        * 传入农历日期数字返回汉字表示法\n        * @param lunar day\n        * @return Cn string\n        * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'\n        */\n    toChinaDay: function (d) { // 日 => \\u65e5\n        var s\n        switch (d) {\n            case 10:\n                s = '\\u521d\\u5341'; break\n            case 20:\n                s = '\\u4e8c\\u5341'; break\n                break\n            case 30:\n                s = '\\u4e09\\u5341'; break\n                break\n            default:\n                s = this.nStr2[Math.floor(d / 10)]\n                s += this.nStr1[d % 10]\n        }\n        return (s)\n    },\n\n    /**\n        * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”\n        * @param y year\n        * @return Cn string\n        * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'\n        */\n    getAnimal: function (y) {\n        return this.Animals[(y - 4) % 12]\n    },\n\n    /**\n        * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON\n        * @param y  solar year\n        * @param m  solar month\n        * @param d  solar day\n        * @return JSON object\n        * @eg:console.log(calendar.solar2lunar(1987,11,01));\n        */\n    solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31\n        // 年份限定、上限\n        if (y < 1900 || y > 2100) {\n            return -1// undefined转换为数字变为NaN\n        }\n        // 公历传参最下限\n        if (y == 1900 && m == 1 && d < 31) {\n            return -1\n        }\n        // 未传参  获得当天\n        if (!y) {\n            var objDate = new Date()\n        } else {\n            var objDate = new Date(y, parseInt(m) - 1, d)\n        }\n        var i; var leap = 0; var temp = 0\n        // 修正ymd参数\n        var y = objDate.getFullYear()\n        var m = objDate.getMonth() + 1\n        var d = objDate.getDate()\n        var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000\n        for (i = 1900; i < 2101 && offset > 0; i++) {\n            temp = this.lYearDays(i)\n            offset -= temp\n        }\n        if (offset < 0) {\n            offset += temp; i--\n        }\n\n        // 是否今天\n        var isTodayObj = new Date()\n        var isToday = false\n        if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {\n            isToday = true\n        }\n        // 星期几\n        var nWeek = objDate.getDay()\n        var cWeek = this.nStr1[nWeek]\n        // 数字表示周几顺应天朝周一开始的惯例\n        if (nWeek == 0) {\n            nWeek = 7\n        }\n        // 农历年\n        var year = i\n        var leap = this.leapMonth(i) // 闰哪个月\n        var isLeap = false\n\n        // 效验闰月\n        for (i = 1; i < 13 && offset > 0; i++) {\n            // 闰月\n            if (leap > 0 && i == (leap + 1) && isLeap == false) {\n                --i\n                isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数\n            } else {\n                temp = this.monthDays(year, i)// 计算农历普通月天数\n            }\n            // 解除闰月\n            if (isLeap == true && i == (leap + 1)) { isLeap = false }\n            offset -= temp\n        }\n        // 闰月导致数组下标重叠取反\n        if (offset == 0 && leap > 0 && i == leap + 1) {\n            if (isLeap) {\n                isLeap = false\n            } else {\n                isLeap = true; --i\n            }\n        }\n        if (offset < 0) {\n            offset += temp; --i\n        }\n        // 农历月\n        var month = i\n        // 农历日\n        var day = offset + 1\n        // 天干地支处理\n        var sm = m - 1\n        var gzY = this.toGanZhiYear(year)\n\n        // 当月的两个节气\n        // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`\n        var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始\n        var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始\n\n        // 依据12节气修正干支月\n        var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)\n        if (d >= firstNode) {\n            gzM = this.toGanZhi((y - 1900) * 12 + m + 12)\n        }\n\n        // 传入的日期的节气与否\n        var isTerm = false\n        var Term = null\n        if (firstNode == d) {\n            isTerm = true\n            Term = this.solarTerm[m * 2 - 2]\n        }\n        if (secondNode == d) {\n            isTerm = true\n            Term = this.solarTerm[m * 2 - 1]\n        }\n        // 日柱 当月一日与 1900/1/1 相差天数\n        var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10\n        var gzD = this.toGanZhi(dayCyclical + d - 1)\n        // 该日期所属的星座\n        var astro = this.toAstro(m, d)\n\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 }\n    },\n\n    /**\n        * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON\n        * @param y  lunar year\n        * @param m  lunar month\n        * @param d  lunar day\n        * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]\n        * @return JSON object\n        * @eg:console.log(calendar.lunar2solar(1987,9,10));\n        */\n    lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1\n        var isLeapMonth = !!isLeapMonth\n        var leapOffset = 0\n        var leapMonth = this.leapMonth(y)\n        var leapDay = this.leapDays(y)\n        if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同\n        if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值\n        var day = this.monthDays(y, m)\n        var _day = day\n        // bugFix 2016-9-25\n        // if month is leap, _day use leapDays method\n        if (isLeapMonth) {\n            _day = this.leapDays(y, m)\n        }\n        if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验\n\n        // 计算农历的时间差\n        var offset = 0\n        for (var i = 1900; i < y; i++) {\n            offset += this.lYearDays(i)\n        }\n        var leap = 0; var isAdd = false\n        for (var i = 1; i < m; i++) {\n            leap = this.leapMonth(y)\n            if (!isAdd) { // 处理闰月\n                if (leap <= i && leap > 0) {\n                    offset += this.leapDays(y); isAdd = true\n                }\n            }\n            offset += this.monthDays(y, i)\n        }\n        // 转换闰月农历 需补充该年闰月的前一个月的时差\n        if (isLeapMonth) { offset += day }\n        // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)\n        var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)\n        var calObj = new Date((offset + d - 31) * 86400000 + stmap)\n        var cY = calObj.getUTCFullYear()\n        var cM = calObj.getUTCMonth() + 1\n        var cD = calObj.getUTCDate()\n\n        return this.solar2lunar(cY, cM, cD)\n    }\n}\n\nexport default calendar\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/util/dayjs.js",
    "content": "!(function (t, e) {\n    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = e() : typeof define === 'function'\n\t\t&& define.amd ? define(e) : t.dayjs = e()\n}(this, () => {\n    'use strict'\n\n    const t = 'millisecond'\n    const e = 'second'\n    const n = 'minute'\n    const r = 'hour'\n    const i = 'day'\n    const s = 'week'\n    const u = 'month'\n    const a = 'quarter'\n    const o = 'year'\n    const f = 'date'\n    const h = /^(\\d{4})[-/]?(\\d{1,2})?[-/]?(\\d{0,2})[^0-9]*(\\d{1,2})?:?(\\d{1,2})?:?(\\d{1,2})?.?(\\d+)?$/\n    const c = /\\[([^\\]]+)]|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\n    const d = {\n        name: 'en',\n        weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),\n        months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_')\n    }\n    const $ = function (t, e, n) {\n        const r = String(t)\n        return !r || r.length >= e ? t : `${Array(e + 1 - r.length).join(n)}${t}`\n    }\n    const l = {\n        s: $,\n        z(t) {\n            const e = -t.utcOffset()\n            const n = Math.abs(e)\n            const r = Math.floor(n / 60)\n            const i = n % 60\n            return `${(e <= 0 ? '+' : '-') + $(r, 2, '0')}:${$(i, 2, '0')}`\n        },\n        m: function t(e, n) {\n            if (e.date() < n.date()) return -t(n, e)\n            const r = 12 * (n.year() - e.year()) + (n.month() - e.month())\n            const i = e.clone().add(r, u)\n            const s = n - i < 0\n            const a = e.clone().add(r + (s ? -1 : 1), u)\n            return +(-(r + (n - i) / (s ? i - a : a - i)) || 0)\n        },\n        a(t) {\n            return t < 0 ? Math.ceil(t) || 0 : Math.floor(t)\n        },\n        p(h) {\n            return {\n                M: u,\n                y: o,\n                w: s,\n                d: i,\n                D: f,\n                h: r,\n                m: n,\n                s: e,\n                ms: t,\n                Q: a\n            }[h] || String(h || '').toLowerCase().replace(/s$/, '')\n        },\n        u(t) {\n            return void 0 === t\n        }\n    }\n    let y = 'en'\n    const M = {}\n    M[y] = d\n    const m = function (t) {\n        return t instanceof S\n    }\n    const D = function (t, e, n) {\n        let r\n        if (!t) return y\n        if (typeof t === 'string') M[t] && (r = t), e && (M[t] = e, r = t)\n        else {\n            const i = t.name\n            M[i] = t, r = i\n        }\n        return !n && r && (y = r), r || !n && y\n    }\n    const v = function (t, e) {\n        if (m(t)) return t.clone()\n        const n = typeof e === 'object' ? e : {}\n        return n.date = t, n.args = arguments, new S(n)\n    }\n    const g = l\n    g.l = D, g.i = m, g.w = function (t, e) {\n        return v(t, {\n            locale: e.$L,\n            utc: e.$u,\n            x: e.$x,\n            $offset: e.$offset\n        })\n    }\n    var S = (function () {\n        function d(t) {\n            this.$L = D(t.locale, null, !0), this.parse(t)\n        }\n        const $ = d.prototype\n        return $.parse = function (t) {\n            this.$d = (function (t) {\n                const e = t.date\n                const n = t.utc\n                if (e === null) return new Date(NaN)\n                if (g.u(e)) return new Date()\n                if (e instanceof Date) return new Date(e)\n                if (typeof e === 'string' && !/Z$/i.test(e)) {\n                    const r = e.match(h)\n                    if (r) {\n                        const i = r[2] - 1 || 0\n                        const s = (r[7] || '0').substring(0, 3)\n                        return n ? new Date(Date.UTC(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s)) : new Date(r[1], i, r[3]\n\t\t\t\t\t\t\t\t|| 1, r[4] || 0, r[5] || 0, r[6] || 0, s)\n                    }\n                }\n                return new Date(e)\n            }(t)), this.$x = t.x || {}, this.init()\n        }, $.init = function () {\n            const t = this.$d\n            this.$y = t.getFullYear(), this.$M = t.getMonth(), this.$D = t.getDate(), this.$W = t.getDay(), this.$H = t.getHours(),\n            this.$m = t.getMinutes(), this.$s = t.getSeconds(), this.$ms = t.getMilliseconds()\n        }, $.$utils = function () {\n            return g\n        }, $.isValid = function () {\n            return !(this.$d.toString() === 'Invalid Date')\n        }, $.isSame = function (t, e) {\n            const n = v(t)\n            return this.startOf(e) <= n && n <= this.endOf(e)\n        }, $.isAfter = function (t, e) {\n            return v(t) < this.startOf(e)\n        }, $.isBefore = function (t, e) {\n            return this.endOf(e) < v(t)\n        }, $.$g = function (t, e, n) {\n            return g.u(t) ? this[e] : this.set(n, t)\n        }, $.unix = function () {\n            return Math.floor(this.valueOf() / 1e3)\n        }, $.valueOf = function () {\n            return this.$d.getTime()\n        }, $.startOf = function (t, a) {\n            const h = this\n            const c = !!g.u(a) || a\n            const d = g.p(t)\n            const $ = function (t, e) {\n                const n = g.w(h.$u ? Date.UTC(h.$y, e, t) : new Date(h.$y, e, t), h)\n                return c ? n : n.endOf(i)\n            }\n            const l = function (t, e) {\n                return g.w(h.toDate()[t].apply(h.toDate('s'), (c ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e)), h)\n            }\n            const y = this.$W\n            const M = this.$M\n            const m = this.$D\n            const D = `set${this.$u ? 'UTC' : ''}`\n            switch (d) {\n            case o:\n                return c ? $(1, 0) : $(31, 11)\n            case u:\n                return c ? $(1, M) : $(0, M + 1)\n            case s:\n                var v = this.$locale().weekStart || 0\n                var S = (y < v ? y + 7 : y) - v\n                return $(c ? m - S : m + (6 - S), M)\n            case i:\n            case f:\n                return l(`${D}Hours`, 0)\n            case r:\n                return l(`${D}Minutes`, 1)\n            case n:\n                return l(`${D}Seconds`, 2)\n            case e:\n                return l(`${D}Milliseconds`, 3)\n            default:\n                return this.clone()\n            }\n        }, $.endOf = function (t) {\n            return this.startOf(t, !1)\n        }, $.$set = function (s, a) {\n            let h; const c = g.p(s)\n            const d = `set${this.$u ? 'UTC' : ''}`\n            const $ = (h = {}, h[i] = `${d}Date`, h[f] = `${d}Date`, h[u] = `${d}Month`, h[o] = `${d}FullYear`, h[r] = `${d}Hours`,\n            h[n] = `${d}Minutes`, h[e] = `${d}Seconds`, h[t] = `${d}Milliseconds`, h)[c]\n            const l = c === i ? this.$D + (a - this.$W) : a\n            if (c === u || c === o) {\n                const y = this.clone().set(f, 1)\n                y.$d[$](l), y.init(), this.$d = y.set(f, Math.min(this.$D, y.daysInMonth())).$d\n            } else $ && this.$d[$](l)\n            return this.init(), this\n        }, $.set = function (t, e) {\n            return this.clone().$set(t, e)\n        }, $.get = function (t) {\n            return this[g.p(t)]()\n        }, $.add = function (t, a) {\n            let f; const\n                h = this\n            t = Number(t)\n            const c = g.p(a)\n            const d = function (e) {\n                const n = v(h)\n                return g.w(n.date(n.date() + Math.round(e * t)), h)\n            }\n            if (c === u) return this.set(u, this.$M + t)\n            if (c === o) return this.set(o, this.$y + t)\n            if (c === i) return d(1)\n            if (c === s) return d(7)\n            const $ = (f = {}, f[n] = 6e4, f[r] = 36e5, f[e] = 1e3, f)[c] || 1\n            const l = this.$d.getTime() + t * $\n            return g.w(l, this)\n        }, $.subtract = function (t, e) {\n            return this.add(-1 * t, e)\n        }, $.format = function (t) {\n            const e = this\n            if (!this.isValid()) return 'Invalid Date'\n            const n = t || 'YYYY-MM-DDTHH:mm:ssZ'\n            const r = g.z(this)\n            const i = this.$locale()\n            const s = this.$H\n            const u = this.$m\n            const a = this.$M\n            const o = i.weekdays\n            const f = i.months\n            const h = function (t, r, i, s) {\n                return t && (t[r] || t(e, n)) || i[r].substr(0, s)\n            }\n            const d = function (t) {\n                return g.s(s % 12 || 12, t, '0')\n            }\n            const $ = i.meridiem || function (t, e, n) {\n                const r = t < 12 ? 'AM' : 'PM'\n                return n ? r.toLowerCase() : r\n            }\n            const l = {\n                YY: String(this.$y).slice(-2),\n                YYYY: this.$y,\n                M: a + 1,\n                MM: g.s(a + 1, 2, '0'),\n                MMM: h(i.monthsShort, a, f, 3),\n                MMMM: h(f, a),\n                D: this.$D,\n                DD: g.s(this.$D, 2, '0'),\n                d: String(this.$W),\n                dd: h(i.weekdaysMin, this.$W, o, 2),\n                ddd: h(i.weekdaysShort, this.$W, o, 3),\n                dddd: o[this.$W],\n                H: String(s),\n                HH: g.s(s, 2, '0'),\n                h: d(1),\n                hh: d(2),\n                a: $(s, u, !0),\n                A: $(s, u, !1),\n                m: String(u),\n                mm: g.s(u, 2, '0'),\n                s: String(this.$s),\n                ss: g.s(this.$s, 2, '0'),\n                SSS: g.s(this.$ms, 3, '0'),\n                Z: r\n            }\n            return n.replace(c, (t, e) => e || l[t] || r.replace(':', ''))\n        }, $.utcOffset = function () {\n            return 15 * -Math.round(this.$d.getTimezoneOffset() / 15)\n        }, $.diff = function (t, f, h) {\n            let c; const d = g.p(f)\n            const $ = v(t)\n            const l = 6e4 * ($.utcOffset() - this.utcOffset())\n            const y = this - $\n            let M = g.m(this, $)\n            return M = (c = {}, c[o] = M / 12, c[u] = M, c[a] = M / 3, c[s] = (y - l) / 6048e5, c[i] = (y - l) / 864e5, c[r] =\t\t\t\t\ty / 36e5, c[n] = y / 6e4, c[e] = y / 1e3, c)[d] || y, h ? M : g.a(M)\n        }, $.daysInMonth = function () {\n            return this.endOf(u).$D\n        }, $.$locale = function () {\n            return M[this.$L]\n        }, $.locale = function (t, e) {\n            if (!t) return this.$L\n            const n = this.clone()\n            const r = D(t, e, !0)\n            return r && (n.$L = r), n\n        }, $.clone = function () {\n            return g.w(this.$d, this)\n        }, $.toDate = function () {\n            return new Date(this.valueOf())\n        }, $.toJSON = function () {\n            return this.isValid() ? this.toISOString() : null\n        }, $.toISOString = function () {\n            return this.$d.toISOString()\n        }, $.toString = function () {\n            return this.$d.toUTCString()\n        }, d\n    }())\n    const p = S.prototype\n    return v.prototype = p, [\n        ['$ms', t],\n        ['$s', e],\n        ['$m', n],\n        ['$H', r],\n        ['$W', i],\n        ['$M', u],\n        ['$y', o],\n        ['$D', f]\n    ].forEach((t) => {\n        p[t[1]] = function (e) {\n            return this.$g(e, t[0], t[1])\n        }\n    }), v.extend = function (t, e) {\n        return t.$i || (t(e, S, v), t.$i = !0), v\n    }, v.locale = D, v.isDayjs = m, v.unix = function (t) {\n        return v(1e3 * t)\n    }, v.en = M[y], v.Ls = M, v.p = {}, v\n}))\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/util/emitter.js",
    "content": "/**\n * 递归使用 call 方式this指向\n * @param componentName // 需要找的组件的名称\n * @param eventName // 事件名称\n * @param params // 需要传递的参数\n */\nfunction broadcast(componentName, eventName, params) {\n    // 循环子节点找到名称一样的子节点 否则 递归 当前子节点\n    this.$children.map((child) => {\n        if (componentName === child.$options.name) {\n            child.$emit.apply(child, [eventName].concat(params))\n        } else {\n            broadcast.apply(child, [componentName, eventName].concat(params))\n        }\n    })\n}\nexport default {\n    methods: {\n        /**\n         * 派发 (向上查找) (一个)\n         * @param componentName // 需要找的组件的名称\n         * @param eventName // 事件名称\n         * @param params // 需要传递的参数\n         */\n        dispatch(componentName, eventName, params) {\n            let parent = this.$parent || this.$root// $parent 找到最近的父节点 $root 根节点\n            let { name } = parent.$options // 获取当前组件实例的name\n            // 如果当前有节点 && 当前没名称 且 当前名称等于需要传进来的名称的时候就去查找当前的节点\n            // 循环出当前名称的一样的组件实例\n            while (parent && (!name || name !== componentName)) {\n                parent = parent.$parent\n                if (parent) {\n                    name = parent.$options.name\n                }\n            }\n            // 有节点表示当前找到了name一样的实例\n            if (parent) {\n                parent.$emit.apply(parent, [eventName].concat(params))\n            }\n        },\n        /**\n         * 广播 (向下查找) (广播多个)\n         * @param componentName // 需要找的组件的名称\n         * @param eventName // 事件名称\n         * @param params // 需要传递的参数\n         */\n        broadcast(componentName, eventName, params) {\n            broadcast.call(this, componentName, eventName, params)\n        }\n    }\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/libs/util/route.js",
    "content": "/**\n * 路由跳转方法，该方法相对于直接使用uni.xxx的好处是使用更加简单快捷\n * 并且带有路由拦截功能\n */\n\nclass Router {\n    constructor() {\n        // 原始属性定义\n        this.config = {\n            type: 'navigateTo',\n            url: '',\n            delta: 1, // navigateBack页面后退时,回退的层数\n            params: {}, // 传递的参数\n            animationType: 'pop-in', // 窗口动画,只在APP有效\n            animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效\n            intercept: false // 是否需要拦截\n        }\n        // 因为route方法是需要对外赋值给另外的对象使用，同时route内部有使用this，会导致route失去上下文\n        // 这里在构造函数中进行this绑定\n        this.route = this.route.bind(this)\n    }\n\n    // 判断url前面是否有\"/\"，如果没有则加上，否则无法跳转\n    addRootPath(url) {\n        return url[0] === '/' ? url : `/${url}`\n    }\n\n    // 整合路由参数\n    mixinParam(url, params) {\n        url = url && this.addRootPath(url)\n\n        // 使用正则匹配，主要依据是判断是否有\"/\",\"?\",\"=\"等，如“/page/index/index?name=mary\"\n        // 如果有url中有get参数，转换后无需带上\"?\"\n        let query = ''\n        if (/.*\\/.*\\?.*=.*/.test(url)) {\n            // object对象转为get类型的参数\n            query = uni.$u.queryParams(params, false)\n            // 因为已有get参数,所以后面拼接的参数需要带上\"&\"隔开\n            return url += `&${query}`\n        }\n        // 直接拼接参数，因为此处url中没有后面的query参数，也就没有\"?/&\"之类的符号\n        query = uni.$u.queryParams(params)\n        return url += query\n    }\n\n    // 对外的方法名称\n    async route(options = {}, params = {}) {\n        // 合并用户的配置和内部的默认配置\n        let mergeConfig = {}\n\n        if (typeof options === 'string') {\n            // 如果options为字符串，则为route(url, params)的形式\n            mergeConfig.url = this.mixinParam(options, params)\n            mergeConfig.type = 'navigateTo'\n        } else {\n            mergeConfig = uni.$u.deepMerge(options, this.config)\n            // 否则正常使用mergeConfig中的url和params进行拼接\n            mergeConfig.url = this.mixinParam(options.url, options.params)\n        }\n\n        // 如果本次跳转的路径和本页面路径一致，不执行跳转，防止用户快速点击跳转按钮，造成多次跳转同一个页面的问题\n        if (mergeConfig.url === uni.$u.page()) return\n\n        if (params.intercept) {\n            this.config.intercept = params.intercept\n        }\n        // params参数也带给拦截器\n        mergeConfig.params = params\n        // 合并内外部参数\n        mergeConfig = uni.$u.deepMerge(this.config, mergeConfig)\n        // 判断用户是否定义了拦截器\n        if (typeof uni.$u.routeIntercept === 'function') {\n            // 定一个promise，根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转\n            const isNext = await new Promise((resolve, reject) => {\n                uni.$u.routeIntercept(mergeConfig, resolve)\n            })\n            // 如果isNext为true，则执行路由跳转\n            isNext && this.openPage(mergeConfig)\n        } else {\n            this.openPage(mergeConfig)\n        }\n    }\n\n    // 执行路由跳转\n    openPage(config) {\n        // 解构参数\n        const {\n            url,\n            type,\n            delta,\n            animationType,\n            animationDuration\n        } = config\n        if (config.type == 'navigateTo' || config.type == 'to') {\n            uni.navigateTo({\n                url,\n                animationType,\n                animationDuration\n            })\n        }\n        if (config.type == 'redirectTo' || config.type == 'redirect') {\n            uni.redirectTo({\n                url\n            })\n        }\n        if (config.type == 'switchTab' || config.type == 'tab') {\n            uni.switchTab({\n                url\n            })\n        }\n        if (config.type == 'reLaunch' || config.type == 'launch') {\n            uni.reLaunch({\n                url\n            })\n        }\n        if (config.type == 'navigateBack' || config.type == 'back') {\n            uni.navigateBack({\n                delta\n            })\n        }\n    }\n}\n\nexport default (new Router()).route\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/package.json",
    "content": "{\n\t\"id\": \"uview-ui\",\n\t\"name\": \"uview-ui\",\n\t\"displayName\": \"uView2.0重磅发布，利剑出鞘，一统江湖\",\n\t\"version\": \"2.0.34\",\n\t\"description\": \"uView UI已完美兼容nvue，全面的组件和便捷的工具会让您信手拈来，如鱼得水\",\n\t\"keywords\": [\n        \"uview\",\n        \"uview\",\n        \"ui\",\n        \"ui\",\n        \"uni-app\",\n        \"uni-app\",\n        \"ui\"\n    ],\n\t\"repository\": \"https://github.com/umicro/uView2.0\",\n\t\"engines\": {\n\t\t\"HBuilderX\": \"^3.1.0\"\n\t},\n    \"dcloudext\": {\n        \"sale\": {\n\t\t\t\"regular\": {\n\t\t\t\t\"price\": \"0.00\"\n\t\t\t},\n\t\t\t\"sourcecode\": {\n\t\t\t\t\"price\": \"0.00\"\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"qq\": \"1416956117\"\n\t\t},\n\t\t\"declaration\": {\n\t\t\t\"ads\": \"无\",\n\t\t\t\"data\": \"无\",\n\t\t\t\"permissions\": \"无\"\n\t\t},\n        \"npmurl\": \"https://www.npmjs.com/package/uview-ui\",\n        \"type\": \"component-vue\"\n\t},\n\t\"uni_modules\": {\n\t\t\"dependencies\": [],\n\t\t\"encrypt\": [],\n\t\t\"platforms\": {\n\t\t\t\"cloud\": {\n\t\t\t\t\"tcb\": \"y\",\n\t\t\t\t\"aliyun\": \"y\"\n\t\t\t},\n\t\t\t\"client\": {\n\t\t\t\t\"Vue\": {\n\t\t\t\t\t\"vue2\": \"y\",\n\t\t\t\t\t\"vue3\": \"n\"\n\t\t\t\t},\n\t\t\t\t\"App\": {\n\t\t\t\t\t\"app-vue\": \"y\",\n\t\t\t\t\t\"app-nvue\": \"y\"\n\t\t\t\t},\n\t\t\t\t\"H5-mobile\": {\n\t\t\t\t\t\"Safari\": \"y\",\n\t\t\t\t\t\"Android Browser\": \"y\",\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\n\t\t\t\t},\n\t\t\t\t\"H5-pc\": {\n\t\t\t\t\t\"Chrome\": \"y\",\n\t\t\t\t\t\"IE\": \"y\",\n\t\t\t\t\t\"Edge\": \"y\",\n\t\t\t\t\t\"Firefox\": \"y\",\n\t\t\t\t\t\"Safari\": \"y\"\n\t\t\t\t},\n\t\t\t\t\"小程序\": {\n\t\t\t\t\t\"微信\": \"y\",\n\t\t\t\t\t\"阿里\": \"y\",\n\t\t\t\t\t\"百度\": \"y\",\n\t\t\t\t\t\"字节跳动\": \"y\",\n\t\t\t\t\t\"QQ\": \"y\"\n\t\t\t\t},\n\t\t\t\t\"快应用\": {\n\t\t\t\t\t\"华为\": \"y\",\n\t\t\t\t\t\"联盟\": \"y\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "bookkeeping_user_uniapp/uni_modules/uview-ui/theme.scss",
    "content": "// 此文件为uView的主题变量，这些变量目前只能通过uni.scss引入才有效，另外由于\n// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中，造成微信程序包太大，\n// 故uni.scss只建议放scss变量名相关样式，其他的样式可以通过main.js或者App.vue引入\n\n$u-main-color: #303133;\n$u-content-color: #606266;\n$u-tips-color: #909193;\n$u-light-color: #c0c4cc;\n$u-border-color: #dadbde;\n$u-bg-color: #f3f4f6;\n$u-disabled-color: #c8c9cc;\n\n$u-primary: #3c9cff;\n$u-primary-dark: #398ade;\n$u-primary-disabled: #9acafc;\n$u-primary-light: #ecf5ff;\n\n$u-warning: #f9ae3d;\n$u-warning-dark: #f1a532;\n$u-warning-disabled: #f9d39b;\n$u-warning-light: #fdf6ec;\n\n$u-success: #5ac725;\n$u-success-dark: #53c21d;\n$u-success-disabled: #a9e08f;\n$u-success-light: #f5fff0;\n\n$u-error: #f56c6c;\n$u-error-dark: #e45656;\n$u-error-disabled: #f7b2b2;\n$u-error-light: #fef0f0;\n\n$u-info: #909399;\n$u-info-dark: #767a82;\n$u-info-disabled: #c4c6c9;\n$u-info-light: #f4f4f5;\n\n// scss混入，为了少写几行#ifndef\n@mixin flex($direction: row) {\n\t/* #ifndef APP-NVUE */\n\tdisplay: flex;\n\t/* #endif */\n\tflex-direction: $direction;\n}\n"
  },
  {
    "path": "docker/mysql/bookkeeping.sql",
    "content": "-- phpMyAdmin SQL Dump\n-- version 5.2.0\n-- https://www.phpmyadmin.net/\n--\n-- Host: mysql\n-- Generation Time: Oct 28, 2022 at 01:27 AM\n-- Server version: 8.0.31\n-- PHP Version: 8.0.19\n\nSET SQL_MODE = \"NO_AUTO_VALUE_ON_ZERO\";\nSTART TRANSACTION;\nSET time_zone = \"+00:00\";\n\n\n/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;\n/*!40101 SET NAMES utf8mb4 */;\n\n--\n-- Database: `bookkeeping`\n--\n\n--\n-- Dumping data for table `t_account`\n--\n\nINSERT INTO `t_account` (`type`, `id`, `enable`, `name`, `notes`, `balance`, `currency_code`, `expenseable`, `include`, `incomeable`, `initial_balance`, `no`, `transfer_from_able`, `transfer_to_able`, `bill_day`, `credit_limit`, `as_of_date`, `apr`, `group_id`) VALUES\n(1, 1, b'1', '现金', NULL, '990.00', 'CNY', b'1', b'1', b'1', '1000.00', NULL, b'1', b'1', NULL, NULL, NULL, NULL, 1);\n\n--\n-- Dumping data for table `t_balance_flow`\n--\n\nINSERT INTO `t_balance_flow` (`type`, `id`, `amount`, `converted_amount`, `create_time`, `description`, `notes`, `status`, `book_id`, `account_id`, `creator_id`, `group_id`, `payee_id`, `to_id`) VALUES\n(1, 1, '10.00', '10.00', 1666920219045, NULL, NULL, 1, 1, 1, 1, 1, NULL, NULL);\n\n--\n-- Dumping data for table `t_book`\n--\n\nINSERT INTO `t_book` (`id`, `enable`, `name`, `notes`, `description_enable`, `image_enable`, `time_enable`, `default_expense_account_id`, `default_expense_category_id`, `default_income_account_id`, `default_income_category_id`, `default_transfer_from_account_id`, `default_transfer_to_account_id`, `group_id`) VALUES\n(1, b'1', '默认账本', NULL, b'1', b'0', b'0', NULL, NULL, NULL, NULL, NULL, NULL, 1);\n\n--\n-- Dumping data for table `t_category`\n--\n\nINSERT INTO `t_category` (`type`, `id`, `enable`, `name`, `notes`, `level`, `book_id`, `parent_id`) VALUES\n(1, 1, b'1', '支出分类1', NULL, 0, 1, NULL),\n(1, 2, b'1', '支出分类2', NULL, 0, 1, NULL),\n(2, 3, b'1', '收入分类1', NULL, 0, 1, NULL),\n(2, 4, b'1', '收入分类2', NULL, 0, 1, NULL);\n\n--\n-- Dumping data for table `t_category_relation`\n--\n\nINSERT INTO `t_category_relation` (`id`, `amount`, `converted_amount`, `category_id`, `deal_id`) VALUES\n(1, '10.00', '10.00', 1, 1);\n\n--\n-- Dumping data for table `t_currency`\n--\n\nINSERT INTO `t_currency` (`id`, `code`, `description`, `rate`) VALUES\n(1, 'AED', 'United Arab Emirates Dirham', '3.67'),\n(2, 'AFN', 'Afghan Afghani', '89.63'),\n(3, 'ALL', 'Albanian Lek', '114.56'),\n(4, 'AMD', 'Armenian Dram', '431.03'),\n(5, 'ANG', 'Netherlands Antillean Guilder', '1.82'),\n(6, 'AOA', 'Angolan Kwanza', '435.61'),\n(7, 'ARS', 'Argentine Peso', '122.70'),\n(8, 'AUD', 'Australian Dollar', '1.42'),\n(9, 'AWG', 'Aruban Florin', '1.80'),\n(10, 'AZN', 'Azerbaijani Manat', '1.70'),\n(11, 'BAM', 'Bosnia-Herzegovina Convertible Mark', '1.87'),\n(12, 'BBD', 'Barbadian Dollar', '2.00'),\n(13, 'BDT', 'Bangladeshi Taka', '93.90'),\n(14, 'BGN', 'Bulgarian Lev', '1.86'),\n(15, 'BHD', 'Bahraini Dinar', '0.38'),\n(16, 'BIF', 'Burundian Franc', '2076.58'),\n(17, 'BMD', 'Bermudan Dollar', '1.00'),\n(18, 'BND', 'Brunei Dollar', '1.39'),\n(19, 'BOB', 'Bolivian Boliviano', '6.95'),\n(20, 'BRL', 'Brazilian Real', '4.98'),\n(21, 'BSD', 'Bahamian Dollar', '1.00'),\n(22, 'BTC', 'Bitcoin', '0.00'),\n(23, 'BTN', 'Bhutanese Ngultrum', '78.56'),\n(24, 'BWP', 'Botswanan Pula', '12.19'),\n(25, 'BYN', 'Belarusian Ruble', '3.41'),\n(26, 'BZD', 'Belize Dollar', '2.04'),\n(27, 'CAD', 'Canadian Dollar', '1.28'),\n(28, 'CDF', 'Congolese Franc', '2019.70'),\n(29, 'CHF', 'Swiss Franc', '0.99'),\n(30, 'CLF', 'Chilean Unit of Account (UF)', '0.03'),\n(31, 'CLP', 'Chilean Peso', '844.22'),\n(32, 'CNH', 'Chinese Yuan (Offshore)', '6.75'),\n(33, 'CNY', 'Chinese Yuan', '6.73'),\n(34, 'COP', 'Colombian Peso', '3887.61'),\n(35, 'CRC', 'Costa Rican Colón', '693.32'),\n(36, 'CUC', 'Cuban Convertible Peso', '1.00'),\n(37, 'CUP', 'Cuban Peso', '25.74'),\n(38, 'CVE', 'Cape Verdean Escudo', '105.22'),\n(39, 'CZK', 'Czech Republic Koruna', '23.53'),\n(40, 'DJF', 'Djiboutian Franc', '179.77'),\n(41, 'DKK', 'Danish Krone', '7.09'),\n(42, 'DOP', 'Dominican Peso', '55.54'),\n(43, 'DZD', 'Algerian Dinar', '146.45'),\n(44, 'EGP', 'Egyptian Pound', '18.88'),\n(45, 'ERN', 'Eritrean Nakfa', '14.99'),\n(46, 'ETB', 'Ethiopian Birr', '52.53'),\n(47, 'EUR', 'Euro', '0.95'),\n(48, 'FJD', 'Fijian Dollar', '2.18'),\n(49, 'FKP', 'Falkland Islands Pound', '0.81'),\n(50, 'GBP', 'British Pound Sterling', '0.81'),\n(51, 'GEL', 'Georgian Lari', '2.91'),\n(52, 'GGP', 'Guernsey Pound', '0.81'),\n(53, 'GHS', 'Ghanaian Cedi', '8.00'),\n(54, 'GIP', 'Gibraltar Pound', '0.81'),\n(55, 'GMD', 'Gambian Dalasi', '54.02'),\n(56, 'GNF', 'Guinean Franc', '8938.62'),\n(57, 'GTQ', 'Guatemalan Quetzal', '7.80'),\n(58, 'GYD', 'Guyanaese Dollar', '211.39'),\n(59, 'HKD', 'Hong Kong Dollar', '7.85'),\n(60, 'HNL', 'Honduran Lempira', '24.81'),\n(61, 'HRK', 'Croatian Kuna', '7.17'),\n(62, 'HTG', 'Haitian Gourde', '115.62'),\n(63, 'HUF', 'Hungarian Forint', '379.32'),\n(64, 'IDR', 'Indonesian Rupiah', '14668.43'),\n(65, 'ILS', 'Israeli New Sheqel', '3.41'),\n(66, 'IMP', 'Manx pound', '0.81'),\n(67, 'INR', 'Indian Rupee', '78.19'),\n(68, 'IQD', 'Iraqi Dinar', '1473.89'),\n(69, 'IRR', 'Iranian Rial', '42329.25'),\n(70, 'ISK', 'Icelandic Króna', '132.16'),\n(71, 'JEP', 'Jersey Pound', '0.81'),\n(72, 'JMD', 'Jamaican Dollar', '154.87'),\n(73, 'JOD', 'Jordanian Dinar', '0.71'),\n(74, 'JPY', 'Japanese Yen', '134.95'),\n(75, 'KES', 'Kenyan Shilling', '118.20'),\n(76, 'KGS', 'Kyrgystani Som', '79.47'),\n(77, 'KHR', 'Cambodian Riel', '4101.51'),\n(78, 'KMF', 'Comorian Franc', '468.02'),\n(79, 'KPW', 'North Korean Won', '899.56'),\n(80, 'KRW', 'South Korean Won', '1284.43'),\n(81, 'KWD', 'Kuwaiti Dinar', '0.31'),\n(82, 'KYD', 'Cayman Islands Dollar', '0.84'),\n(83, 'KZT', 'Kazakhstani Tenge', '440.52'),\n(84, 'LAK', 'Laotian Kip', '14536.13'),\n(85, 'LBP', 'Lebanese Pound', '1526.93'),\n(86, 'LKR', 'Sri Lankan Rupee', '362.01'),\n(87, 'LRD', 'Liberian Dollar', '151.93'),\n(88, 'LSL', 'Lesotho Loti', '15.75'),\n(89, 'LYD', 'Libyan Dinar', '4.83'),\n(90, 'MAD', 'Moroccan Dirham', '10.01'),\n(91, 'MDL', 'Moldovan Leu', '19.24'),\n(92, 'MGA', 'Malagasy Ariary', '4093.55'),\n(93, 'MKD', 'Macedonian Denar', '58.74'),\n(94, 'MMK', 'Myanma Kyat', '1869.58'),\n(95, 'MNT', 'Mongolian Tugrik', '3101.86'),\n(96, 'MOP', 'Macanese Pataca', '8.16'),\n(97, 'MRO', 'Mauritanian Ouguiya (pre-2018)', '6.80'),\n(98, 'MRU', 'Mauritanian Ouguiya', '36.78'),\n(99, 'MUR', 'Mauritian Rupee', '44.23'),\n(100, 'MVR', 'Maldivian Rufiyaa', '15.85'),\n(101, 'MWK', 'Malawian Kwacha', '1031.56'),\n(102, 'MXN', 'Mexican Peso', '20.10'),\n(103, 'MYR', 'Malaysian Ringgit', '4.41'),\n(104, 'MZN', 'Mozambican Metical', '63.87'),\n(105, 'NAD', 'Namibian Dollar', '15.85'),\n(106, 'NGN', 'Nigerian Naira', '414.93'),\n(107, 'NIO', 'Nicaraguan Córdoba', '36.20'),\n(108, 'NOK', 'Norwegian Krone', '9.77'),\n(109, 'NPR', 'Nepalese Rupee', '125.69'),\n(110, 'NZD', 'New Zealand Dollar', '1.58'),\n(111, 'OMR', 'Omani Rial', '0.39'),\n(112, 'PAB', 'Panamanian Balboa', '1.00'),\n(113, 'PEN', 'Peruvian Nuevo Sol', '3.80'),\n(114, 'PGK', 'Papua New Guinean Kina', '3.60'),\n(115, 'PHP', 'Philippine Peso', '53.22'),\n(116, 'PKR', 'Pakistani Rupee', '204.23'),\n(117, 'PLN', 'Polish Zloty', '4.40'),\n(118, 'PYG', 'Paraguayan Guarani', '6927.60'),\n(119, 'QAR', 'Qatari Rial', '3.69'),\n(120, 'RON', 'Romanian Leu', '4.70'),\n(121, 'RSD', 'Serbian Dinar', '111.51'),\n(122, 'RUB', 'Russian Ruble', '57.07'),\n(123, 'RWF', 'Rwandan Franc', '1038.64'),\n(124, 'SAR', 'Saudi Riyal', '3.75'),\n(125, 'SBD', 'Solomon Islands Dollar', '8.11'),\n(126, 'SCR', 'Seychellois Rupee', '14.24'),\n(127, 'SDG', 'Sudanese Pound', '456.28'),\n(128, 'SEK', 'Swedish Krona', '10.06'),\n(129, 'SGD', 'Singapore Dollar', '1.39'),\n(130, 'SHP', 'Saint Helena Pound', '0.81'),\n(131, 'SLL', 'Sierra Leonean Leone', '13123.57'),\n(132, 'SOS', 'Somali Shilling', '584.12'),\n(133, 'SRD', 'Surinamese Dollar', '21.76'),\n(134, 'SSP', 'South Sudanese Pound', '130.20'),\n(135, 'STD', 'São Tomé and Príncipe Dobra (pre-2018)', '23251.60'),\n(136, 'STN', 'São Tomé and Príncipe Dobra', '23.49'),\n(137, 'SVC', 'Salvadoran Colón', '8.84'),\n(138, 'SYP', 'Syrian Pound', '2511.30'),\n(139, 'SZL', 'Swazi Lilangeni', '15.75'),\n(140, 'THB', 'Thai Baht', '34.78'),\n(141, 'TJS', 'Tajikistani Somoni', '11.11'),\n(142, 'TMT', 'Turkmenistani Manat', '3.50'),\n(143, 'TND', 'Tunisian Dinar', '3.07'),\n(144, 'TOP', 'Tongan Pa\\'anga', '2.32'),\n(145, 'TRY', 'Turkish Lira', '17.22'),\n(146, 'TTD', 'Trinidad and Tobago Dollar', '6.86'),\n(147, 'TWD', 'New Taiwan Dollar', '29.73'),\n(148, 'TZS', 'Tanzanian Shilling', '2352.70'),\n(149, 'UAH', 'Ukrainian Hryvnia', '29.84'),\n(150, 'UGX', 'Ugandan Shilling', '3726.33'),\n(151, 'USD', 'United States Dollar', '1.00'),\n(152, 'UYU', 'Uruguayan Peso', '39.89'),\n(153, 'UZS', 'Uzbekistan Som', '11113.27'),\n(154, 'VEF', 'Venezuelan Bolívar Fuerte (Old)', '6.80'),\n(155, 'VES', 'Venezuelan Bolívar Soberano', '5.24'),\n(156, 'VND', 'Vietnamese Dong', '23198.56'),\n(157, 'VUV', 'Vanuatu Vatu', '115.00'),\n(158, 'WST', 'Samoan Tala', '2.62'),\n(159, 'XAF', 'CFA Franc BEAC', '624.89'),\n(160, 'XAG', 'Silver Ounce', '0.05'),\n(161, 'XAU', 'Gold Ounce', '0.00'),\n(162, 'XCD', 'East Caribbean Dollar', '2.70'),\n(163, 'XDR', 'Special Drawing Rights', '0.73'),\n(164, 'XOF', 'CFA Franc BCEAO', '624.89'),\n(165, 'XPD', 'Palladium Ounce', '0.00'),\n(166, 'XPF', 'CFP Franc', '113.68'),\n(167, 'XPT', 'Platinum Ounce', '0.00'),\n(168, 'YER', 'Yemeni Rial', '250.13'),\n(169, 'ZAR', 'South African Rand', '15.97'),\n(170, 'ZMW', 'Zambian Kwacha', '17.09'),\n(171, 'ZWL', 'Zimbabwean Dollar', '321.84');\n\n--\n-- Dumping data for table `t_group`\n--\n\nINSERT INTO `t_group` (`id`, `enable`, `name`, `notes`, `avatar`, `default_currency_code`, `default_book_id`) VALUES\n(1, b'1', '默认组', NULL, NULL, 'CNY', 1);\n\n--\n-- Dumping data for table `t_user`\n--\n\nINSERT INTO `t_user` (`id`, `avatar`, `email`, `enable`, `ip`, `nick_name`, `password`, `telephone`, `user_name`, `vip_time`, `default_book_id`, `default_group_id`) VALUES\n(1, NULL, NULL, b'1', '172.27.0.6', '111', '96E79218965EB72C92A549DD5A330112', NULL, '111', 1666920131036, 1, 1);\n\n--\n-- Dumping data for table `t_user_group_relation`\n--\n\nINSERT INTO `t_user_group_relation` (`id`, `role`, `group_id`, `user_id`) VALUES\n(1, 1, 1, 1);\nCOMMIT;\n\n/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"
  },
  {
    "path": "docker/mysql/config/my.cnf",
    "content": "[mysqld]\ncharacter-set-server=utf8mb4\ndefault-time-zone='+8:00'\ninnodb_rollback_on_timeout='ON'\nmax_connections=500\ninnodb_lock_wait_timeout=500"
  },
  {
    "path": "docker/mysql/currency.sql",
    "content": "\nINSERT INTO `t_currency` (`id`, `code`, `description`, `rate`) VALUES\n(1, 'AED', 'United Arab Emirates Dirham', '3.67'),\n(2, 'AFN', 'Afghan Afghani', '89.63'),\n(3, 'ALL', 'Albanian Lek', '114.56'),\n(4, 'AMD', 'Armenian Dram', '431.03'),\n(5, 'ANG', 'Netherlands Antillean Guilder', '1.82'),\n(6, 'AOA', 'Angolan Kwanza', '435.61'),\n(7, 'ARS', 'Argentine Peso', '122.70'),\n(8, 'AUD', 'Australian Dollar', '1.42'),\n(9, 'AWG', 'Aruban Florin', '1.80'),\n(10, 'AZN', 'Azerbaijani Manat', '1.70'),\n(11, 'BAM', 'Bosnia-Herzegovina Convertible Mark', '1.87'),\n(12, 'BBD', 'Barbadian Dollar', '2.00'),\n(13, 'BDT', 'Bangladeshi Taka', '93.90'),\n(14, 'BGN', 'Bulgarian Lev', '1.86'),\n(15, 'BHD', 'Bahraini Dinar', '0.38'),\n(16, 'BIF', 'Burundian Franc', '2076.58'),\n(17, 'BMD', 'Bermudan Dollar', '1.00'),\n(18, 'BND', 'Brunei Dollar', '1.39'),\n(19, 'BOB', 'Bolivian Boliviano', '6.95'),\n(20, 'BRL', 'Brazilian Real', '4.98'),\n(21, 'BSD', 'Bahamian Dollar', '1.00'),\n(22, 'BTC', 'Bitcoin', '0.00'),\n(23, 'BTN', 'Bhutanese Ngultrum', '78.56'),\n(24, 'BWP', 'Botswanan Pula', '12.19'),\n(25, 'BYN', 'Belarusian Ruble', '3.41'),\n(26, 'BZD', 'Belize Dollar', '2.04'),\n(27, 'CAD', 'Canadian Dollar', '1.28'),\n(28, 'CDF', 'Congolese Franc', '2019.70'),\n(29, 'CHF', 'Swiss Franc', '0.99'),\n(30, 'CLF', 'Chilean Unit of Account (UF)', '0.03'),\n(31, 'CLP', 'Chilean Peso', '844.22'),\n(32, 'CNH', 'Chinese Yuan (Offshore)', '6.75'),\n(33, 'CNY', 'Chinese Yuan', '6.73'),\n(34, 'COP', 'Colombian Peso', '3887.61'),\n(35, 'CRC', 'Costa Rican Colón', '693.32'),\n(36, 'CUC', 'Cuban Convertible Peso', '1.00'),\n(37, 'CUP', 'Cuban Peso', '25.74'),\n(38, 'CVE', 'Cape Verdean Escudo', '105.22'),\n(39, 'CZK', 'Czech Republic Koruna', '23.53'),\n(40, 'DJF', 'Djiboutian Franc', '179.77'),\n(41, 'DKK', 'Danish Krone', '7.09'),\n(42, 'DOP', 'Dominican Peso', '55.54'),\n(43, 'DZD', 'Algerian Dinar', '146.45'),\n(44, 'EGP', 'Egyptian Pound', '18.88'),\n(45, 'ERN', 'Eritrean Nakfa', '14.99'),\n(46, 'ETB', 'Ethiopian Birr', '52.53'),\n(47, 'EUR', 'Euro', '0.95'),\n(48, 'FJD', 'Fijian Dollar', '2.18'),\n(49, 'FKP', 'Falkland Islands Pound', '0.81'),\n(50, 'GBP', 'British Pound Sterling', '0.81'),\n(51, 'GEL', 'Georgian Lari', '2.91'),\n(52, 'GGP', 'Guernsey Pound', '0.81'),\n(53, 'GHS', 'Ghanaian Cedi', '8.00'),\n(54, 'GIP', 'Gibraltar Pound', '0.81'),\n(55, 'GMD', 'Gambian Dalasi', '54.02'),\n(56, 'GNF', 'Guinean Franc', '8938.62'),\n(57, 'GTQ', 'Guatemalan Quetzal', '7.80'),\n(58, 'GYD', 'Guyanaese Dollar', '211.39'),\n(59, 'HKD', 'Hong Kong Dollar', '7.85'),\n(60, 'HNL', 'Honduran Lempira', '24.81'),\n(61, 'HRK', 'Croatian Kuna', '7.17'),\n(62, 'HTG', 'Haitian Gourde', '115.62'),\n(63, 'HUF', 'Hungarian Forint', '379.32'),\n(64, 'IDR', 'Indonesian Rupiah', '14668.43'),\n(65, 'ILS', 'Israeli New Sheqel', '3.41'),\n(66, 'IMP', 'Manx pound', '0.81'),\n(67, 'INR', 'Indian Rupee', '78.19'),\n(68, 'IQD', 'Iraqi Dinar', '1473.89'),\n(69, 'IRR', 'Iranian Rial', '42329.25'),\n(70, 'ISK', 'Icelandic Króna', '132.16'),\n(71, 'JEP', 'Jersey Pound', '0.81'),\n(72, 'JMD', 'Jamaican Dollar', '154.87'),\n(73, 'JOD', 'Jordanian Dinar', '0.71'),\n(74, 'JPY', 'Japanese Yen', '134.95'),\n(75, 'KES', 'Kenyan Shilling', '118.20'),\n(76, 'KGS', 'Kyrgystani Som', '79.47'),\n(77, 'KHR', 'Cambodian Riel', '4101.51'),\n(78, 'KMF', 'Comorian Franc', '468.02'),\n(79, 'KPW', 'North Korean Won', '899.56'),\n(80, 'KRW', 'South Korean Won', '1284.43'),\n(81, 'KWD', 'Kuwaiti Dinar', '0.31'),\n(82, 'KYD', 'Cayman Islands Dollar', '0.84'),\n(83, 'KZT', 'Kazakhstani Tenge', '440.52'),\n(84, 'LAK', 'Laotian Kip', '14536.13'),\n(85, 'LBP', 'Lebanese Pound', '1526.93'),\n(86, 'LKR', 'Sri Lankan Rupee', '362.01'),\n(87, 'LRD', 'Liberian Dollar', '151.93'),\n(88, 'LSL', 'Lesotho Loti', '15.75'),\n(89, 'LYD', 'Libyan Dinar', '4.83'),\n(90, 'MAD', 'Moroccan Dirham', '10.01'),\n(91, 'MDL', 'Moldovan Leu', '19.24'),\n(92, 'MGA', 'Malagasy Ariary', '4093.55'),\n(93, 'MKD', 'Macedonian Denar', '58.74'),\n(94, 'MMK', 'Myanma Kyat', '1869.58'),\n(95, 'MNT', 'Mongolian Tugrik', '3101.86'),\n(96, 'MOP', 'Macanese Pataca', '8.16'),\n(97, 'MRO', 'Mauritanian Ouguiya (pre-2018)', '6.80'),\n(98, 'MRU', 'Mauritanian Ouguiya', '36.78'),\n(99, 'MUR', 'Mauritian Rupee', '44.23'),\n(100, 'MVR', 'Maldivian Rufiyaa', '15.85'),\n(101, 'MWK', 'Malawian Kwacha', '1031.56'),\n(102, 'MXN', 'Mexican Peso', '20.10'),\n(103, 'MYR', 'Malaysian Ringgit', '4.41'),\n(104, 'MZN', 'Mozambican Metical', '63.87'),\n(105, 'NAD', 'Namibian Dollar', '15.85'),\n(106, 'NGN', 'Nigerian Naira', '414.93'),\n(107, 'NIO', 'Nicaraguan Córdoba', '36.20'),\n(108, 'NOK', 'Norwegian Krone', '9.77'),\n(109, 'NPR', 'Nepalese Rupee', '125.69'),\n(110, 'NZD', 'New Zealand Dollar', '1.58'),\n(111, 'OMR', 'Omani Rial', '0.39'),\n(112, 'PAB', 'Panamanian Balboa', '1.00'),\n(113, 'PEN', 'Peruvian Nuevo Sol', '3.80'),\n(114, 'PGK', 'Papua New Guinean Kina', '3.60'),\n(115, 'PHP', 'Philippine Peso', '53.22'),\n(116, 'PKR', 'Pakistani Rupee', '204.23'),\n(117, 'PLN', 'Polish Zloty', '4.40'),\n(118, 'PYG', 'Paraguayan Guarani', '6927.60'),\n(119, 'QAR', 'Qatari Rial', '3.69'),\n(120, 'RON', 'Romanian Leu', '4.70'),\n(121, 'RSD', 'Serbian Dinar', '111.51'),\n(122, 'RUB', 'Russian Ruble', '57.07'),\n(123, 'RWF', 'Rwandan Franc', '1038.64'),\n(124, 'SAR', 'Saudi Riyal', '3.75'),\n(125, 'SBD', 'Solomon Islands Dollar', '8.11'),\n(126, 'SCR', 'Seychellois Rupee', '14.24'),\n(127, 'SDG', 'Sudanese Pound', '456.28'),\n(128, 'SEK', 'Swedish Krona', '10.06'),\n(129, 'SGD', 'Singapore Dollar', '1.39'),\n(130, 'SHP', 'Saint Helena Pound', '0.81'),\n(131, 'SLL', 'Sierra Leonean Leone', '13123.57'),\n(132, 'SOS', 'Somali Shilling', '584.12'),\n(133, 'SRD', 'Surinamese Dollar', '21.76'),\n(134, 'SSP', 'South Sudanese Pound', '130.20'),\n(135, 'STD', 'São Tomé and Príncipe Dobra (pre-2018)', '23251.60'),\n(136, 'STN', 'São Tomé and Príncipe Dobra', '23.49'),\n(137, 'SVC', 'Salvadoran Colón', '8.84'),\n(138, 'SYP', 'Syrian Pound', '2511.30'),\n(139, 'SZL', 'Swazi Lilangeni', '15.75'),\n(140, 'THB', 'Thai Baht', '34.78'),\n(141, 'TJS', 'Tajikistani Somoni', '11.11'),\n(142, 'TMT', 'Turkmenistani Manat', '3.50'),\n(143, 'TND', 'Tunisian Dinar', '3.07'),\n(144, 'TOP', 'Tongan Pa\\'anga', '2.32'),\n(145, 'TRY', 'Turkish Lira', '17.22'),\n(146, 'TTD', 'Trinidad and Tobago Dollar', '6.86'),\n(147, 'TWD', 'New Taiwan Dollar', '29.73'),\n(148, 'TZS', 'Tanzanian Shilling', '2352.70'),\n(149, 'UAH', 'Ukrainian Hryvnia', '29.84'),\n(150, 'UGX', 'Ugandan Shilling', '3726.33'),\n(151, 'USD', 'United States Dollar', '1.00'),\n(152, 'UYU', 'Uruguayan Peso', '39.89'),\n(153, 'UZS', 'Uzbekistan Som', '11113.27'),\n(154, 'VEF', 'Venezuelan Bolívar Fuerte (Old)', '6.80'),\n(155, 'VES', 'Venezuelan Bolívar Soberano', '5.24'),\n(156, 'VND', 'Vietnamese Dong', '23198.56'),\n(157, 'VUV', 'Vanuatu Vatu', '115.00'),\n(158, 'WST', 'Samoan Tala', '2.62'),\n(159, 'XAF', 'CFA Franc BEAC', '624.89'),\n(160, 'XAG', 'Silver Ounce', '0.05'),\n(161, 'XAU', 'Gold Ounce', '0.00'),\n(162, 'XCD', 'East Caribbean Dollar', '2.70'),\n(163, 'XDR', 'Special Drawing Rights', '0.73'),\n(164, 'XOF', 'CFA Franc BCEAO', '624.89'),\n(165, 'XPD', 'Palladium Ounce', '0.00'),\n(166, 'XPF', 'CFP Franc', '113.68'),\n(167, 'XPT', 'Platinum Ounce', '0.00'),\n(168, 'YER', 'Yemeni Rial', '250.13'),\n(169, 'ZAR', 'South African Rand', '15.97'),\n(170, 'ZMW', 'Zambian Kwacha', '17.09'),\n(171, 'ZWL', 'Zimbabwean Dollar', '321.84');\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "version: '3.0'\n\nservices:\n  mysql:\n    image: mysql:8.0\n    volumes:\n      - mysql_data:/var/lib/mysql\n    environment:\n      - MYSQL_ROOT_PASSWORD=tn8o4Ak9\n\n  phpmyadmin:\n    depends_on:\n      - mysql\n    image: phpmyadmin/phpmyadmin\n    ports:\n      - \"8085:80\"\n    environment:\n      PMA_ARBITRARY: 1\n      PMA_HOST: mysql\n      PMA_USER: root\n      PMA_PASSWORD: tn8o4Ak9\n\n  user-api:\n    image: markliu2018/bookkeeping-user-api:latest\n    depends_on:\n      - mysql\n    environment:\n      DB_HOST: mysql\n      DB_PORT: 3306\n      DB_NAME: bookkeeping\n      DB_USER: root\n      DB_PASSWORD: tn8o4Ak9\n      UPLOAD_ACCESS_KEY: XXX\n      UPLOAD_SECRET_KEY: XXX\n      UPLOAD_FLOW_IMAGE_BUCKET: XXX\n      UPLOAD_FLOW_IMAGE_HOST: http://XXX\n      UPLOAD_FLOW_IMAGE_CALL_BACK_URL: http://XXX/api/v1/flow-images/upload-callback\n      INVITE_CODE: 1111\n\n  user-fe:\n    image: markliu2018/bookkeeping-user-fe:latest\n    depends_on:\n      - user-api\n    environment:\n      API_HOST: http://user-api:9092\n    ports:\n      - \"5000:80\"\n\nvolumes:\n  mysql_data:"
  },
  {
    "path": "docker-compose2.yaml",
    "content": "version: '3.0'\n\nservices:\n  mysql:\n    image: mysql:8.0\n    volumes:\n      - mysql_data:/var/lib/mysql\n      - ./docker/mysql/config:/etc/mysql/conf.d\n    environment:\n      - MYSQL_ROOT_PASSWORD=tn8o4Ak9\n\n  phpmyadmin:\n    depends_on:\n      - mysql\n    image: phpmyadmin/phpmyadmin\n    ports:\n      - \"8085:80\"\n    environment:\n      PMA_ARBITRARY: 1\n      PMA_HOST: mysql\n      PMA_USER: root\n      PMA_PASSWORD: tn8o4Ak9\n\n  user-api:\n    build: ./bookkeeping-user-api/\n    depends_on:\n      - mysql\n    environment:\n      DB_HOST: mysql\n      DB_PORT: 3306\n      DB_NAME: bookkeeping\n      DB_USER: root\n      DB_PASSWORD: tn8o4Ak9\n      UPLOAD_ACCESS_KEY: XXXXXXXXXXXXXXXXXX\n      UPLOAD_SECRET_KEY: XXXXXXXXXXXXXXXXXX\n      UPLOAD_FLOW_IMAGE_BUCKET: XXXXXXXXXXXXXXXXXX\n      UPLOAD_FLOW_IMAGE_HOST: XXXXXXXXXXXXXXXXXX\n      UPLOAD_FLOW_IMAGE_CALL_BACK_URL: XXXXXXXXXXXXXXXXXX/api/v1/flow-images/upload-callback\n\n  user-fe:\n    build: ./bookkeeping-user-fe/\n    depends_on:\n      - user-api\n    environment:\n      API_HOST: http://user-api:9092\n    ports:\n      - \"5000:80\"\n\nvolumes:\n  mysql_data:\n"
  },
  {
    "path": "notes.txt",
    "content": "Spring Boot Docker  https://spring.io/guides/topicals/spring-boot-docker/\n"
  }
]