Compare commits
35
Commits
main
..
9649b6e465
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9649b6e465 | ||
|
|
c87546ca0d | ||
|
|
aef0abbecf | ||
|
|
c5818b7ec4 | ||
|
|
80b0c29ada | ||
|
|
c082b3070e | ||
|
|
8c0887e705 | ||
|
|
e133b6b99a | ||
|
|
90f561a0c9 | ||
|
|
eb5f5e37b5 | ||
|
|
2cb3429dcd | ||
|
|
182c7a3873 | ||
|
|
9a004a3133 | ||
|
|
b122567c12 | ||
|
|
f62672d779 | ||
|
|
40367a4e12 | ||
|
|
31404cf225 | ||
|
|
8c7422dc68 | ||
|
|
795620b0f8 | ||
|
|
1e24287327 | ||
|
|
c22a509633 | ||
|
|
1170d5238a | ||
|
|
813a317ff6 | ||
|
|
ceb0b33656 | ||
|
|
439daf3b33 | ||
|
|
435e56b02a | ||
|
|
45ab672fed | ||
|
|
148957a8ae | ||
|
|
83fe44d7be | ||
|
|
c045214969 | ||
|
|
7b1910a7a7 | ||
|
|
abf030baa6 | ||
|
|
7466af0368 | ||
|
|
cc867c471e | ||
|
|
0c4e94b5af |
+3
-171
@@ -1,173 +1,5 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
runtime/
|
||||
.idea/
|
||||
.vscode/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# ---> Vue
|
||||
# gitignore template for Vue.js projects
|
||||
#
|
||||
# Recommended template: Node.gitignore
|
||||
|
||||
# TODO: where does this rule come from?
|
||||
docs/_book
|
||||
|
||||
# TODO: where does this rule come from?
|
||||
test/
|
||||
|
||||
|
||||
@@ -1,2 +1,40 @@
|
||||
# VerifVATInvoice
|
||||
# 发票核验完整工程示例
|
||||
|
||||
该项目为VerifyVATInvoice的完整工程示例。
|
||||
|
||||
该示例**无法在线调试**,如需调试可下载到本地后替换 [AK](https://usercenter.console.aliyun.com/#/manage/ak) 以及参数后进行调试。
|
||||
|
||||
## 运行条件
|
||||
|
||||
- 下载并解压需要语言的代码;
|
||||
|
||||
|
||||
- 在阿里云帐户中获取您的 [凭证](https://usercenter.console.aliyun.com/#/manage/ak) 并通过它替换下载后代码中的 ACCESS_KEY_ID 以及 ACCESS_KEY_SECRET;
|
||||
|
||||
- 执行对应语言的构建及运行语句
|
||||
|
||||
## 执行步骤
|
||||
|
||||
下载的代码包,在根据自己需要更改代码中的参数和 AK 以后,可以在**解压代码所在目录下**按如下的步骤执行:
|
||||
|
||||
- *Python 版本要求 Python3*
|
||||
```sh
|
||||
python3 setup.py install && python ./alibabacloud_sample/sample.py
|
||||
```
|
||||
## 使用的 API
|
||||
|
||||
- VerifyVATInvoice:发票核验接口支持包括:增值税专用发票、增值税普通发票(折叠票)、增值税普通发票(卷票)、增值税电子普通发票(含收费公路通行费增值税电子普通发票)、机动车销售统一发票、二手车销售统一发票多种类型发票核验。您可以通过输入发票的关键验证字段,返回真实的票面信息,包括发票类型、发票代码、发票号码、作废标志、开票日期、购方税号及其他发票信息等。当天开具发票当日可查验(T+0)。注意:可能有几小时到十几小时的延迟。 更多信息可参考:[文档](https://next.api.aliyun.com/document/ocr-api/2021-07-07/VerifyVATInvoice)
|
||||
|
||||
## API 返回示例
|
||||
|
||||
*实际输出结构可能稍有不同,属于正常返回;下列输出值仅作为参考,以实际调用为准*
|
||||
|
||||
|
||||
- JSON 格式
|
||||
```js
|
||||
{
|
||||
"RequestId": "43A29C77-405E-4CC0-BC55-EE694AD00655",
|
||||
"Data": "{\n \"code\": \"001\",\n \"data\": {\n \"afterTaxCode\": \"\",\n \"allTax\": \"9.67\",\n \"allValoremTax\": \"332.00\",\n \"blueInvoiceCode\": \"\",\n \"blueInvoiceNo\": \"\",\n \"brandVersion\": \"\",\n \"businessUnit\": \"\",\n \"businessUnitTaxNo\": \"\",\n \"busmessUnitAddress\": \"\",\n \"busmessUnitBankAndAccount\": \"\",\n \"busmessUnitPhone\": \"\",\n \"carPrice\": \"\",\n \"carType\": \"\",\n \"carTypeAndNumber\": \"\",\n \"carframeCode\": \"\",\n \"carrierName\": \"\",\n \"carrierTaxNo\": \"\",\n \"checkCode\": \"07122942791187744XXXX\",\n \"code\": \"\",\n \"consignorName\": \"\",\n \"consignorTaxNo\": \"\",\n \"cyjgxx\": \"查验成功发票一致\",\n \"detailList\": [\n {\n \"allTax\": \"9.67\",\n \"detailAmount\": \"322.33\",\n \"detailNo\": \"1\",\n \"expenseItem\": \"\",\n \"goodsName\": \"*餐饮服务*餐费\",\n \"netValue\": \"322.330097\",\n \"num\": \"1\",\n \"plate_no\": \"\",\n \"rowNo\": \"1\",\n \"standard\": \"\",\n \"taxClassifyCode\": \"0\",\n \"taxDetailAmount\": \"\",\n \"taxRate\": \"3\",\n \"taxUnitPrice\": \"\",\n \"trafficDateEnd\": \"\",\n \"trafficDateStart\": \"\",\n \"type\": \"\",\n \"unit\": \"\"\n }\n ],\n \"draweeName\": \"\",\n \"draweeTaxNo\": \"\",\n \"engineCode\": \"\",\n \"idCard\": \"\",\n \"importLicense\": \"\",\n \"inspectionAmount\": \"\",\n \"inspectionNumber\": \"\",\n \"invalidMark\": \"N\",\n \"invoiceCode\": \"01100180XXXX\",\n \"invoiceDate\": \"2018XXXX\",\n \"invoiceMoney\": \"322.XX\",\n \"invoiceNumber\": \"3531XXXX\",\n \"invoiceType\": \"10\",\n \"lemonMarket\": \"\",\n \"lemonMarketAddress\": \"\",\n \"lemonMarketBankAndAccount\": \"\",\n \"lemonMarketPhone\": \"\",\n \"lemonMarketTaxNo\": \"\",\n \"licenseCode\": \"\",\n \"licensePlate\": \"\",\n \"limitAmount\": \"\",\n \"machineCode\": \"49992273XXXX\",\n \"note\": \"机器编号:49992273XXXX\",\n \"producingArea\": \"\",\n \"purchaserAddressOrPhone\": \"杭州余杭区XXXX0571-8502XXXX\",\n \"purchaserBankAndNumber\": \"招商银行杭州高新支行571906593XXXXXX\",\n \"purchaserName\": \"XXXX软件有限公司\",\n \"purchaserPhone\": \"\",\n \"purchaserTaxpayerNumber\": \"913301007682XXXXXX\",\n \"purchaserUnitOrIndividual\": \"\",\n \"purchaserUnitOrIndividualAddress\": \"\",\n \"purchaserUnitcodeOrIdNo\": \"\",\n \"receiveName\": \"\",\n \"receiveTaxNo\": \"\",\n \"registrationNo\": \"\",\n \"salerAddress\": \"\",\n \"salerAddressOrPhone\": \"北京市海淀区XXX8211XXXX\",\n \"salerBankAccount\": \"中国银行海淀支行345456XXXXXX\",\n \"salerBankAndNumber\": \"中国银行海淀支行345456XXXXXX\",\n \"salerBankName\": \"\",\n \"salerName\": \"北京市XXX酒家\",\n \"salerPhone\": \"\",\n \"salerTaxpayerNumber\": \"911101081020XXXXXX\",\n \"sellerPhone\": \"\",\n \"sellerUnitCodeOrIdno\": \"\",\n \"sellerUnitOrIndividual\": \"北京市XXX酒家\",\n \"sellerUnitOrIndividualAddress\": \"\",\n \"taxDiskNumber\": \"\",\n \"taxRate\": \"\",\n \"taxUnitCode\": \"\",\n \"taxUnitName\": \"\",\n \"throughAddress\": \"\",\n \"trafficFeeFlag\": \"\",\n \"transferredVehicleOffice\": \"\",\n \"transportGoodsInfo\": \"\",\n \"unit\": \"\",\n \"vehicleTonnage\": \"\",\n \"zeroTaxRateFlag\": \"\"\n },\n \"msg\": \"成功\"\n}"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
Metadata-Version: 2.4
|
||||
Name: alibabacloud_sample
|
||||
Version: 1.0.0
|
||||
Summary: Alibaba Cloud SDK Code Sample Library for Python
|
||||
Home-page: https://github.com/aliyun/alibabacloud-sdk
|
||||
Author:
|
||||
Author-email:
|
||||
License: Apache License 2.0
|
||||
Keywords: alibabacloud,sample
|
||||
Platform: any
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Topic :: Software Development
|
||||
Requires-Python: >=3.6
|
||||
Description-Content-Type: text/markdown
|
||||
Requires-Dist: alibabacloud_ocr-api20210707<4.0.0,>=3.1.3
|
||||
Requires-Dist: alibabacloud_tea_openapi<1.0.0,>=0.3.15
|
||||
Requires-Dist: alibabacloud_tea_console<1.0.0,>=0.0.1
|
||||
Requires-Dist: alibabacloud_tea_util<1.0.0,>=0.3.13
|
||||
Requires-Dist: alibabacloud_credentials<2.0.0,>=1.0.2
|
||||
Requires-Dist: flask
|
||||
Dynamic: classifier
|
||||
Dynamic: description
|
||||
Dynamic: description-content-type
|
||||
Dynamic: home-page
|
||||
Dynamic: keywords
|
||||
Dynamic: license
|
||||
Dynamic: platform
|
||||
Dynamic: requires-dist
|
||||
Dynamic: requires-python
|
||||
Dynamic: summary
|
||||
|
||||
# 发票核验完整工程示例
|
||||
|
||||
该项目为VerifyVATInvoice的完整工程示例。
|
||||
|
||||
该示例**无法在线调试**,如需调试可下载到本地后替换 [AK](https://usercenter.console.aliyun.com/#/manage/ak) 以及参数后进行调试。
|
||||
|
||||
## 运行条件
|
||||
|
||||
- 下载并解压需要语言的代码;
|
||||
|
||||
|
||||
- 在阿里云帐户中获取您的 [凭证](https://usercenter.console.aliyun.com/#/manage/ak) 并通过它替换下载后代码中的 ACCESS_KEY_ID 以及 ACCESS_KEY_SECRET;
|
||||
|
||||
- 执行对应语言的构建及运行语句
|
||||
|
||||
## 执行步骤
|
||||
|
||||
下载的代码包,在根据自己需要更改代码中的参数和 AK 以后,可以在**解压代码所在目录下**按如下的步骤执行:
|
||||
|
||||
- *Python 版本要求 Python3*
|
||||
```sh
|
||||
python3 setup.py install && python ./alibabacloud_sample/sample.py
|
||||
```
|
||||
## 使用的 API
|
||||
|
||||
- VerifyVATInvoice:发票核验接口支持包括:增值税专用发票、增值税普通发票(折叠票)、增值税普通发票(卷票)、增值税电子普通发票(含收费公路通行费增值税电子普通发票)、机动车销售统一发票、二手车销售统一发票多种类型发票核验。您可以通过输入发票的关键验证字段,返回真实的票面信息,包括发票类型、发票代码、发票号码、作废标志、开票日期、购方税号及其他发票信息等。当天开具发票当日可查验(T+0)。注意:可能有几小时到十几小时的延迟。 更多信息可参考:[文档](https://next.api.aliyun.com/document/ocr-api/2021-07-07/VerifyVATInvoice)
|
||||
|
||||
## API 返回示例
|
||||
|
||||
*实际输出结构可能稍有不同,属于正常返回;下列输出值仅作为参考,以实际调用为准*
|
||||
|
||||
|
||||
- JSON 格式
|
||||
```js
|
||||
{
|
||||
"RequestId": "43A29C77-405E-4CC0-BC55-EE694AD00655",
|
||||
"Data": "{\n \"code\": \"001\",\n \"data\": {\n \"afterTaxCode\": \"\",\n \"allTax\": \"9.67\",\n \"allValoremTax\": \"332.00\",\n \"blueInvoiceCode\": \"\",\n \"blueInvoiceNo\": \"\",\n \"brandVersion\": \"\",\n \"businessUnit\": \"\",\n \"businessUnitTaxNo\": \"\",\n \"busmessUnitAddress\": \"\",\n \"busmessUnitBankAndAccount\": \"\",\n \"busmessUnitPhone\": \"\",\n \"carPrice\": \"\",\n \"carType\": \"\",\n \"carTypeAndNumber\": \"\",\n \"carframeCode\": \"\",\n \"carrierName\": \"\",\n \"carrierTaxNo\": \"\",\n \"checkCode\": \"07122942791187744XXXX\",\n \"code\": \"\",\n \"consignorName\": \"\",\n \"consignorTaxNo\": \"\",\n \"cyjgxx\": \"查验成功发票一致\",\n \"detailList\": [\n {\n \"allTax\": \"9.67\",\n \"detailAmount\": \"322.33\",\n \"detailNo\": \"1\",\n \"expenseItem\": \"\",\n \"goodsName\": \"*餐饮服务*餐费\",\n \"netValue\": \"322.330097\",\n \"num\": \"1\",\n \"plate_no\": \"\",\n \"rowNo\": \"1\",\n \"standard\": \"\",\n \"taxClassifyCode\": \"0\",\n \"taxDetailAmount\": \"\",\n \"taxRate\": \"3\",\n \"taxUnitPrice\": \"\",\n \"trafficDateEnd\": \"\",\n \"trafficDateStart\": \"\",\n \"type\": \"\",\n \"unit\": \"\"\n }\n ],\n \"draweeName\": \"\",\n \"draweeTaxNo\": \"\",\n \"engineCode\": \"\",\n \"idCard\": \"\",\n \"importLicense\": \"\",\n \"inspectionAmount\": \"\",\n \"inspectionNumber\": \"\",\n \"invalidMark\": \"N\",\n \"invoiceCode\": \"01100180XXXX\",\n \"invoiceDate\": \"2018XXXX\",\n \"invoiceMoney\": \"322.XX\",\n \"invoiceNumber\": \"3531XXXX\",\n \"invoiceType\": \"10\",\n \"lemonMarket\": \"\",\n \"lemonMarketAddress\": \"\",\n \"lemonMarketBankAndAccount\": \"\",\n \"lemonMarketPhone\": \"\",\n \"lemonMarketTaxNo\": \"\",\n \"licenseCode\": \"\",\n \"licensePlate\": \"\",\n \"limitAmount\": \"\",\n \"machineCode\": \"49992273XXXX\",\n \"note\": \"机器编号:49992273XXXX\",\n \"producingArea\": \"\",\n \"purchaserAddressOrPhone\": \"杭州余杭区XXXX0571-8502XXXX\",\n \"purchaserBankAndNumber\": \"招商银行杭州高新支行571906593XXXXXX\",\n \"purchaserName\": \"XXXX软件有限公司\",\n \"purchaserPhone\": \"\",\n \"purchaserTaxpayerNumber\": \"913301007682XXXXXX\",\n \"purchaserUnitOrIndividual\": \"\",\n \"purchaserUnitOrIndividualAddress\": \"\",\n \"purchaserUnitcodeOrIdNo\": \"\",\n \"receiveName\": \"\",\n \"receiveTaxNo\": \"\",\n \"registrationNo\": \"\",\n \"salerAddress\": \"\",\n \"salerAddressOrPhone\": \"北京市海淀区XXX8211XXXX\",\n \"salerBankAccount\": \"中国银行海淀支行345456XXXXXX\",\n \"salerBankAndNumber\": \"中国银行海淀支行345456XXXXXX\",\n \"salerBankName\": \"\",\n \"salerName\": \"北京市XXX酒家\",\n \"salerPhone\": \"\",\n \"salerTaxpayerNumber\": \"911101081020XXXXXX\",\n \"sellerPhone\": \"\",\n \"sellerUnitCodeOrIdno\": \"\",\n \"sellerUnitOrIndividual\": \"北京市XXX酒家\",\n \"sellerUnitOrIndividualAddress\": \"\",\n \"taxDiskNumber\": \"\",\n \"taxRate\": \"\",\n \"taxUnitCode\": \"\",\n \"taxUnitName\": \"\",\n \"throughAddress\": \"\",\n \"trafficFeeFlag\": \"\",\n \"transferredVehicleOffice\": \"\",\n \"transportGoodsInfo\": \"\",\n \"unit\": \"\",\n \"vehicleTonnage\": \"\",\n \"zeroTaxRateFlag\": \"\"\n },\n \"msg\": \"成功\"\n}"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
README.md
|
||||
setup.py
|
||||
alibabacloud_sample/__init__.py
|
||||
alibabacloud_sample/dataservice.py
|
||||
alibabacloud_sample/dbservice.py
|
||||
alibabacloud_sample/fileservice.py
|
||||
alibabacloud_sample/main.py
|
||||
alibabacloud_sample/service.py
|
||||
alibabacloud_sample.egg-info/PKG-INFO
|
||||
alibabacloud_sample.egg-info/SOURCES.txt
|
||||
alibabacloud_sample.egg-info/dependency_links.txt
|
||||
alibabacloud_sample.egg-info/requires.txt
|
||||
alibabacloud_sample.egg-info/top_level.txt
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
alibabacloud_ocr-api20210707<4.0.0,>=3.1.3
|
||||
alibabacloud_tea_openapi<1.0.0,>=0.3.15
|
||||
alibabacloud_tea_console<1.0.0,>=0.0.1
|
||||
alibabacloud_tea_util<1.0.0,>=0.3.13
|
||||
alibabacloud_credentials<2.0.0,>=1.0.2
|
||||
flask
|
||||
@@ -0,0 +1 @@
|
||||
alibabacloud_sample
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = "1.0.0"
|
||||
@@ -0,0 +1,258 @@
|
||||
# 数据管理服务类
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from dateutil import parser
|
||||
|
||||
from alibabacloud_sample.dbservice import DBService
|
||||
|
||||
|
||||
class DataService:
|
||||
conn = None
|
||||
def __init__(self):
|
||||
self.conn = DBService().get_conn()
|
||||
# 插入文件记录
|
||||
def insert_file_data(self, file_name: str, file_path: str, file_size: int, file_type: str,file_time: str, file_hash: str):
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
try:
|
||||
cursor.execute(
|
||||
"INSERT INTO file_data(file_name,file_path,file_size,file_type,file_time,file_hash) values(?,?,?,?,?,?)",
|
||||
(file_name, file_path, file_size, file_type, file_time, file_hash))
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
conn.rollback()
|
||||
cursor.execute("select file_path from file_data where file_hash = ?", (file_hash,))
|
||||
rows = cursor.fetchall()
|
||||
return rows[0][0]
|
||||
return file_path
|
||||
# 根据文件路径获取hash
|
||||
def get_invoice(self, file_path: str = None,invice_id: int = None) -> dict[Any, Any]:
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
sql = """
|
||||
select id, checkCode, drawer, formType, invoiceAmountPreTax, invoiceCode,
|
||||
invoiceDate, invoiceNumber, invoiceTax, invoiceType, machineCode,
|
||||
passwordArea, printedInvoiceCode, printedInvoiceNumber,
|
||||
purchaserBankAccountInfo, purchaserContactInfo, purchaserName,
|
||||
purchaserTaxNumber, recipient, remarks, reviewer,
|
||||
sellerBankAccountInfo, sellerContactInfo, sellerName, sellerTaxNumber,
|
||||
specialTag, title, totalAmount, totalAmountInWords,file_path,verify_time,
|
||||
verify_time,verify_status,desc,desc_files,status
|
||||
from invoice
|
||||
"""
|
||||
if invice_id:
|
||||
sql += " where id = ?"
|
||||
cursor.execute(sql, (invice_id,))
|
||||
else:
|
||||
sql += " where file_path = ?"
|
||||
cursor.execute(sql, (file_path,))
|
||||
rows = cursor.fetchall()
|
||||
data = {}
|
||||
if len(rows) > 0:
|
||||
for i in range(len(rows[0])):
|
||||
data[cursor.description[i][0]] = rows[0][i]
|
||||
return data
|
||||
|
||||
# 根据文件hash获取文件
|
||||
def get_file(self, file_hash: str,file_size: int) -> dict[Any, Any] | None:
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
select file_name,file_path,file_size,file_type,file_time,file_hash
|
||||
from file_data where file_hash = ? and file_size = ?
|
||||
"""
|
||||
, (file_hash,file_size,))
|
||||
rows = cursor.fetchall()
|
||||
|
||||
if len(rows) > 0:
|
||||
data = {}
|
||||
for i in range(len(rows[0])):
|
||||
data[cursor.description[i][0]] = rows[0][i]
|
||||
return data
|
||||
return None
|
||||
# 插入发票记录
|
||||
def insert_invoice_log(self, data: dict, file_path: str):
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
# 插入主表数据
|
||||
invoice_insert = """
|
||||
INSERT INTO invoice (
|
||||
checkCode, drawer, formType, invoiceAmountPreTax, invoiceCode,
|
||||
invoiceDate, invoiceNumber, invoiceTax, invoiceType, machineCode,
|
||||
passwordArea, printedInvoiceCode, printedInvoiceNumber,
|
||||
purchaserBankAccountInfo, purchaserContactInfo, purchaserName,
|
||||
purchaserTaxNumber, recipient, remarks, reviewer,
|
||||
sellerBankAccountInfo, sellerContactInfo, sellerName, sellerTaxNumber,
|
||||
specialTag, title, totalAmount, totalAmountInWords,file_path
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
invoice_values = (
|
||||
data.get('checkCode', ''),
|
||||
data.get('drawer', ''),
|
||||
data.get('formType', ''),
|
||||
float(data.get('invoiceAmountPreTax', 0.0)),
|
||||
data.get('invoiceCode', ''),
|
||||
data.get('invoiceDate', ''),
|
||||
data.get('invoiceNumber', ''),
|
||||
float(data.get('invoiceTax', 0.0)),
|
||||
data.get('invoiceType', ''),
|
||||
data.get('machineCode', ''),
|
||||
data.get('passwordArea', ''),
|
||||
data.get('printedInvoiceCode', ''),
|
||||
data.get('printedInvoiceNumber', ''),
|
||||
data.get('purchaserBankAccountInfo', ''),
|
||||
data.get('purchaserContactInfo', ''),
|
||||
data.get('purchaserName', ''),
|
||||
data.get('purchaserTaxNumber', ''),
|
||||
data.get('recipient', ''),
|
||||
data.get('remarks', ''),
|
||||
data.get('reviewer', ''),
|
||||
data.get('sellerBankAccountInfo', ''),
|
||||
data.get('sellerContactInfo', ''),
|
||||
data.get('sellerName', ''),
|
||||
data.get('sellerTaxNumber', ''),
|
||||
data.get('specialTag', ''),
|
||||
data.get('title', ''),
|
||||
float(data.get('totalAmount', 0.0)),
|
||||
data.get('totalAmountInWords', ''),
|
||||
file_path
|
||||
)
|
||||
cursor.execute(invoice_insert, invoice_values)
|
||||
conn.commit()
|
||||
invoice_id = cursor.lastrowid
|
||||
return invoice_id
|
||||
# 更新发票状态
|
||||
def update_invoice_status(self, invoice_id: int, verify_status: str,inspectionAmount :str ):
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"UPDATE invoice SET verify_time = ?, "
|
||||
"verify_status = ?, "
|
||||
"inspectionAmount = ? "
|
||||
"WHERE id = ?",
|
||||
(datetime.now().strftime("%Y-%m-%d %H:%M:%S"), verify_status,inspectionAmount, invoice_id))
|
||||
conn.commit()
|
||||
def update_invoice_desc(self, invoice_id: int|str, desc: str, desc_file: str,status: str) -> str:
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"UPDATE invoice SET "
|
||||
"desc = ?,"
|
||||
"desc_files = ?,"
|
||||
"status = ? "
|
||||
"WHERE id = ?",
|
||||
(desc, desc_file,status, invoice_id))
|
||||
conn.commit()
|
||||
return "success"
|
||||
# 查询验证记录
|
||||
def get_verify_log(self, file_path: str) -> dict[Any, Any]|None:
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
select inspectionAmount,cyjgxx,verify_time,file_path from verify_log where file_path = ? order by verify_time desc limit 1
|
||||
"""
|
||||
, (file_path,))
|
||||
rows = cursor.fetchall()
|
||||
if len(rows) > 0:
|
||||
data = {}
|
||||
for i in range(len(rows[0])):
|
||||
data[cursor.description[i][0]] = rows[0][i]
|
||||
return data
|
||||
return None
|
||||
# 插入验证记录
|
||||
def insert_verify_log(self, inspection_amount: str, cyjgxx: str, file_path: str):
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"INSERT INTO verify_log(inspectionAmount,cyjgxx,verify_time,file_path) values(?,?,?,?)",
|
||||
(inspection_amount,cyjgxx,parser.parse(datetime.now().strftime("%Y-%m-%d %H:%M:%S")),file_path))
|
||||
conn.commit()
|
||||
# 查询验证记录
|
||||
def get_verify_log_list(self, file_path: str) -> list[dict[Any, Any]]:
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
select inspectionAmount,cyjgxx,verify_time,file_path from verify_log where file_path = ?
|
||||
"""
|
||||
, (file_path,))
|
||||
rows = cursor.fetchall()
|
||||
data = []
|
||||
for row in rows:
|
||||
data.append({
|
||||
'inspectionAmount': row[0],
|
||||
'cyjgxx': row[1],
|
||||
'verify_time': row[2],
|
||||
'file_path': row[3]
|
||||
})
|
||||
return data
|
||||
# 查询发票列表
|
||||
def get_invoice_list(self, value: str, verify_status: str, time_out = 'all' ,start_date: datetime = None, end_date: datetime = None) -> list[dict[Any, Any]]:
|
||||
conn = DBService().get_conn()
|
||||
cursor = conn.cursor()
|
||||
sql = """
|
||||
select id, checkCode, drawer, formType, invoiceAmountPreTax, invoiceCode,
|
||||
invoiceDate, invoiceNumber, invoiceTax, invoiceType, machineCode,
|
||||
passwordArea, printedInvoiceCode, printedInvoiceNumber,
|
||||
purchaserBankAccountInfo, purchaserContactInfo, purchaserName,
|
||||
purchaserTaxNumber, recipient, remarks, reviewer,
|
||||
sellerBankAccountInfo, sellerContactInfo, sellerName, sellerTaxNumber,
|
||||
specialTag, title, totalAmount, totalAmountInWords,file_path,verify_time,verify_status,inspectionAmount,desc,desc_files,status
|
||||
from invoice where
|
||||
"""
|
||||
if time_out != 'all':
|
||||
sql += " date('now', '-"+time_out+" day') >= date(verify_time) and verify_status == 'success' "
|
||||
elif verify_status != 'all':
|
||||
sql += " verify_status = '" + verify_status+"'"
|
||||
else:
|
||||
sql += " 1 = 1 "
|
||||
if start_date is not None:
|
||||
sql += " and verify_time >= '" + start_date + "'"
|
||||
if end_date is not None:
|
||||
sql += " and verify_time <= '" + end_date + "'"
|
||||
if value is not None and value != '':
|
||||
sql += " and ( "
|
||||
sql += "file_path like '%" + value + "%' or "
|
||||
sql += "checkCode like '%" + value + "%' or "
|
||||
sql += "drawer like '%" + value + "%' or "
|
||||
sql += "formType like '%" + value + "%' or "
|
||||
sql += "invoiceAmountPreTax like '%" + value + "%' or "
|
||||
sql += "invoiceCode like '%" + value + "%' or "
|
||||
sql += "invoiceDate like '%" + value + "%' or "
|
||||
sql += "invoiceNumber like '%" + value + "%' or "
|
||||
sql += "invoiceTax like '%" + value + "%' or "
|
||||
sql += "invoiceType like '%" + value + "%' or "
|
||||
sql += "machineCode like '%" + value + "%' or "
|
||||
sql += "passwordArea like '%" + value + "%' or "
|
||||
sql += "printedInvoiceCode like '%" + value + "%' or "
|
||||
sql += "printedInvoiceNumber like '%" + value + "%' or "
|
||||
sql += "purchaserBankAccountInfo like '%" + value + "%' or "
|
||||
sql += "purchaserContactInfo like '%" + value + "%' or "
|
||||
sql += "purchaserName like '%" + value + "%' or "
|
||||
sql += "purchaserTaxNumber like '%" + value + "%' or "
|
||||
sql += "recipient like '%" + value + "%' or "
|
||||
sql += "remarks like '%" + value + "%' or "
|
||||
sql += "reviewer like '%" + value + "%' or "
|
||||
sql += "sellerBankAccountInfo like '%" + value + "%' or "
|
||||
sql += "sellerContactInfo like '%" + value + "%' or "
|
||||
sql += "sellerName like '%" + value + "%' or "
|
||||
sql += "sellerTaxNumber like '%" + value + "%' or "
|
||||
sql += "specialTag like '%" + value + "%' or "
|
||||
sql += "title like '%" + value + "%' or "
|
||||
sql += "totalAmount like '%" + value + "%' or "
|
||||
sql += "totalAmountInWords like '%" + value + "%' or "
|
||||
sql += "desc like '%" + value + "%' or "
|
||||
sql += "desc_files like '%" + value + "%' "
|
||||
sql += ")"
|
||||
sql += " order by verify_time desc"
|
||||
print(sql)
|
||||
cursor.execute(sql)
|
||||
rows = cursor.fetchall()
|
||||
datas = []
|
||||
for row in rows:
|
||||
data = {}
|
||||
for i in range(len(row)):
|
||||
data[cursor.description[i][0]] = row[i]
|
||||
datas.append(data)
|
||||
return datas
|
||||
@@ -0,0 +1,131 @@
|
||||
import sqlite3
|
||||
from sqlite3 import Error
|
||||
|
||||
|
||||
class DBService:
|
||||
db_file = "/data/verify.db"
|
||||
conn = None
|
||||
def __init__(self):
|
||||
"""
|
||||
初始化函数
|
||||
"""
|
||||
self.conn = self.get_conn()
|
||||
print("DBService init")
|
||||
self.create_file_data_table()
|
||||
self.create_invoice_table()
|
||||
self.create_verify_log_table()
|
||||
pass
|
||||
def get_conn(self):
|
||||
"""
|
||||
创建数据库连接
|
||||
该函数用于创建数据库连接,并返回连接对象
|
||||
返回值:
|
||||
连接对象
|
||||
"""
|
||||
if self.conn is not None:
|
||||
return self.conn
|
||||
try:
|
||||
self.conn = sqlite3.connect(self.db_file)
|
||||
except Error as e:
|
||||
print(e)
|
||||
return self.conn
|
||||
def create_verify_log_table(self):
|
||||
|
||||
"""
|
||||
创建验证记录表
|
||||
该函数用于创建表,并返回创建结果
|
||||
"""
|
||||
try:
|
||||
# delete_table_sql = """DROP TABLE IF EXISTS verify_log;"""
|
||||
# c = self.get_conn().cursor()
|
||||
# c.execute(delete_table_sql)
|
||||
create_table_sql = """CREATE TABLE IF NOT EXISTS verify_log (
|
||||
id integer PRIMARY KEY,
|
||||
cyjgxx text NOT NULL,
|
||||
inspectionAmount text NOT NULL,
|
||||
verify_time text NOT NULL,
|
||||
file_path text NOT NULL,
|
||||
desc text
|
||||
);"""
|
||||
c = self.get_conn().cursor()
|
||||
c.execute(create_table_sql)
|
||||
except Error as e:
|
||||
print(e)
|
||||
# 文件数据表
|
||||
def create_file_data_table(self):
|
||||
"""
|
||||
创建文件数据表
|
||||
该函数用于创建表,并返回创建结果
|
||||
"""
|
||||
try:
|
||||
# delete_table_sql = """DROP TABLE IF EXISTS file_data;"""
|
||||
# c = self.get_conn().cursor()
|
||||
# c.execute(delete_table_sql)
|
||||
create_table_sql = """CREATE TABLE IF NOT EXISTS file_data (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
file_name text NOT NULL,
|
||||
file_path text NOT NULL,
|
||||
file_size integer NOT NULL,
|
||||
file_type text NOT NULL,
|
||||
file_time text NOT NULL,
|
||||
file_hash text NOT NULL,
|
||||
UNIQUE (file_hash) -- 联合唯一约束
|
||||
);"""
|
||||
c = self.get_conn().cursor()
|
||||
c.execute(create_table_sql)
|
||||
self.get_conn().commit()
|
||||
except Error as e:
|
||||
print(e)
|
||||
|
||||
# 发票数据表
|
||||
def create_invoice_table(self):
|
||||
# delete_table_sql = """DROP TABLE IF EXISTS invoice;"""
|
||||
# c = self.get_conn().cursor()
|
||||
# c.execute(delete_table_sql)
|
||||
create_table_sql = """
|
||||
-- 主表存储发票基本信息
|
||||
CREATE TABLE IF NOT EXISTS invoice (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
checkCode TEXT NOT NULL UNIQUE, -- 校验码(唯一)
|
||||
drawer TEXT, -- 开票人
|
||||
formType TEXT, -- 表单类型
|
||||
invoiceAmountPreTax REAL, -- 税前金额
|
||||
invoiceCode TEXT NOT NULL, -- 发票代码
|
||||
invoiceDate TEXT, -- 开票日期
|
||||
invoiceNumber TEXT NOT NULL, -- 发票号码
|
||||
invoiceTax REAL, -- 发票税额
|
||||
invoiceType TEXT, -- 发票类型
|
||||
machineCode TEXT, -- 机器编号
|
||||
passwordArea TEXT, -- 密码区
|
||||
printedInvoiceCode TEXT, -- 打印发票代码
|
||||
printedInvoiceNumber TEXT, -- 打印发票号码
|
||||
purchaserBankAccountInfo TEXT, -- 购买方银行信息
|
||||
purchaserContactInfo TEXT, -- 购买方联系信息
|
||||
purchaserName TEXT, -- 购买方名称
|
||||
purchaserTaxNumber TEXT, -- 购买方税号
|
||||
recipient TEXT, -- 收款人
|
||||
remarks TEXT, -- 备注
|
||||
reviewer TEXT, -- 复核人
|
||||
sellerBankAccountInfo TEXT, -- 销售方银行信息
|
||||
sellerContactInfo TEXT, -- 销售方联系信息
|
||||
sellerName TEXT, -- 销售方名称
|
||||
sellerTaxNumber TEXT, -- 销售方税号
|
||||
specialTag TEXT, -- 特殊标记
|
||||
title TEXT, -- 发票标题
|
||||
totalAmount REAL, -- 合计金额
|
||||
totalAmountInWords TEXT, -- 大写金额
|
||||
file_path TEXT, -- 文件哈希
|
||||
verify_time TEXT, -- 验证时间
|
||||
verify_status TEXT, -- 验证结果
|
||||
inspectionAmount TEXT, -- 验证次数
|
||||
desc TEXT, -- 备注
|
||||
desc_files TEXT, -- 备注附件
|
||||
status TEXT, -- 是否报销(yes,no)
|
||||
UNIQUE (invoiceCode, invoiceNumber) -- 联合唯一约束
|
||||
);
|
||||
"""
|
||||
try:
|
||||
c = self.get_conn().cursor()
|
||||
c.execute(create_table_sql)
|
||||
except Error as e:
|
||||
print(e)
|
||||
@@ -0,0 +1,54 @@
|
||||
# 文件服务接口
|
||||
from typing import Any
|
||||
|
||||
from flask import Request
|
||||
from werkzeug.utils import secure_filename
|
||||
import os , time, hashlib
|
||||
from alibabacloud_sample.dataservice import DataService
|
||||
|
||||
dataservice = DataService()
|
||||
|
||||
class FileService:
|
||||
# 创建文件服务实例
|
||||
def __init__(self):
|
||||
pass
|
||||
# 上传文件
|
||||
def upload(self, request: Request, file_path: str) -> None | str | dict[Any, Any] | Any:
|
||||
if request.method == 'POST':
|
||||
# 检查是否有文件在请求中
|
||||
if 'file' not in request.files:
|
||||
return '没有文件被上传'
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return '请重新选择文件'
|
||||
if file:
|
||||
date = time.strftime("%Y%m%d", time.localtime())
|
||||
filetype = secure_filename(file.filename)
|
||||
filename = str(int(time.time())) + "_" + file.filename
|
||||
filepath = os.path.join(file_path, date, filename)
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
file.save(filepath)
|
||||
file_size = os.path.getsize(filepath)
|
||||
file_hash = self.get_hash(filepath)
|
||||
v_file = dataservice.get_file(file_hash,file_size)
|
||||
if v_file:
|
||||
os.remove(filepath)
|
||||
return v_file
|
||||
else:
|
||||
dataservice.insert_file_data(file.filename, filepath, file_size, filetype, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),file_hash)
|
||||
v_file = dataservice.get_file(file_hash, file_size)
|
||||
print("insert v_file:", v_file)
|
||||
return v_file
|
||||
return None
|
||||
|
||||
# 获取文件md5
|
||||
@staticmethod
|
||||
def get_hash(file_path: str) -> str:
|
||||
with open(file_path, 'rb') as f:
|
||||
md5 = hashlib.md5()
|
||||
while True:
|
||||
data = f.read(8192)
|
||||
if not data:
|
||||
break
|
||||
md5.update(data)
|
||||
return md5.hexdigest()
|
||||
@@ -0,0 +1,97 @@
|
||||
from flask import Flask, request, render_template, send_from_directory
|
||||
from flask_cors import CORS
|
||||
from alibabacloud_sample.dataservice import DataService
|
||||
from alibabacloud_sample.fileservice import FileService
|
||||
from alibabacloud_sample.service import Service
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
app.config['UPLOAD_FOLDER'] = '/data/uploads' # 指定上传文件夹
|
||||
app.config['WEBAPP'] = 'templates'
|
||||
dataservice = DataService()
|
||||
@app.route('/index')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
@app.route('/assets/<path:filename>')
|
||||
def return_js(filename):
|
||||
return send_from_directory(app.config.get('WEBAPP')+'/assets', str(filename))
|
||||
@app.route('/data/uploads/<path:filename>')
|
||||
def return_file(filename):
|
||||
return send_from_directory(app.config.get('UPLOAD_FOLDER'), str(filename))
|
||||
# 上传文件
|
||||
@app.route('/upload', methods=['POST'])
|
||||
def upload_file():
|
||||
if request.method == 'POST':
|
||||
file_service = FileService()
|
||||
msg = file_service.upload(request,app.config['UPLOAD_FOLDER'])
|
||||
if msg == "No file part" or msg == "No selected file":
|
||||
return "请选择文件"
|
||||
else:
|
||||
return msg
|
||||
return None
|
||||
# 发票识别
|
||||
@app.route('/recognize', methods=['GET'])
|
||||
def recognize():
|
||||
file_path = request.args.get('filePath')
|
||||
service = Service()
|
||||
data = dataservice.get_invoice(file_path=file_path)
|
||||
if data is not None and data.get('file_path') is not None:
|
||||
return data
|
||||
return service.recognize(file_path=file_path)
|
||||
# 发票验证
|
||||
@app.route('/verify', methods=['GET'])
|
||||
def verify():
|
||||
file_path = request.args.get('filePath')
|
||||
invoice_id = request.args.get('invoiceId')
|
||||
if file_path is None:
|
||||
return {"msg":"请选择文件","status":'fail'}
|
||||
service = Service()
|
||||
data = dataservice.get_invoice(file_path=file_path,invice_id=invoice_id)
|
||||
if data is not None:
|
||||
if data.get('status') == 'yes':
|
||||
return {"msg":"请勿重复验证","status":'fail'}
|
||||
return service.verify(data=data,file_path=file_path)
|
||||
else:
|
||||
return {"msg":"请先识别发票","status":'fail'}
|
||||
# 发票复验
|
||||
@app.route('/reverify', methods=['GET'])
|
||||
def reverify():
|
||||
file_path = request.args.get('filePath')
|
||||
if file_path is None:
|
||||
return "请选择文件"
|
||||
service = Service()
|
||||
data = dataservice.get_invoice(file_path=file_path)
|
||||
return service.verify(data=data,file_path=file_path,reverify=True)
|
||||
# 发票列表
|
||||
@app.route('/listInvoice',methods=['POST'])
|
||||
def list_invoice():
|
||||
value = request.json.get('value')
|
||||
verify_status = request.json.get('verify_status')
|
||||
verify_time = request.json.get('verify_time')
|
||||
start_date = None
|
||||
end_date = None
|
||||
if verify_time is not None:
|
||||
start_date = verify_time[0].split(' ')[0]
|
||||
end_date = verify_time[1].split(' ')[0]
|
||||
return dataservice.get_invoice_list(value=value,verify_status=verify_status,start_date=start_date,end_date=end_date)
|
||||
@app.route('/listInvoiceTimeOut',methods=['POST'])
|
||||
def list_invoice_time_out():
|
||||
time_out = request.form.get('timeOut')
|
||||
return dataservice.get_invoice_list(value='',verify_status='',time_out=time_out)
|
||||
# 发票日志列表
|
||||
@app.route('/listLogs',methods=['GET'])
|
||||
def list_logs():
|
||||
file_path = request.args.get('filePath')
|
||||
return dataservice.get_verify_log_list(file_path=file_path)
|
||||
# 更新发票标注
|
||||
@app.route('/updateInvoice',methods=['POST'])
|
||||
def update_invoice():
|
||||
invoice_id = request.json.get('invoiceId')
|
||||
status = request.json.get('status')
|
||||
desc = request.json.get('desc')
|
||||
desc_files = request.json.get('desc_files')
|
||||
return dataservice.update_invoice_desc(invoice_id=invoice_id,desc=desc,desc_file=desc_files,status=status)
|
||||
if __name__ == '__main__':
|
||||
# Service.verify(sys.argv[1:])
|
||||
# Service.recognize(file_path="/home/jayus/图片/wechat_2025-07-31_131911_822.png")
|
||||
app.run(host='0.0.0.0', port=5555, debug=True)
|
||||
@@ -0,0 +1,148 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# This file is auto-generated, don't edit it. Thanks.
|
||||
import os
|
||||
import statistics
|
||||
import sys
|
||||
|
||||
from typing import List
|
||||
|
||||
from alibabacloud_credentials.models import Config
|
||||
from alibabacloud_ocr_api20210707.client import Client as ocr_api20210707Client, Client
|
||||
from alibabacloud_credentials.client import Client as CredentialClient
|
||||
from alibabacloud_tea_openapi import models as open_api_models
|
||||
from alibabacloud_ocr_api20210707 import models as ocr_api_20210707_models
|
||||
from alibabacloud_tea_util import models as util_models
|
||||
from alibabacloud_tea_console.client import Client as ConsoleClient
|
||||
from alibabacloud_tea_util.client import Client as UtilClient
|
||||
from alibabacloud_darabonba_stream.client import Client as StreamClient
|
||||
from flask import json
|
||||
|
||||
from alibabacloud_sample.dataservice import DataService
|
||||
|
||||
|
||||
class Service:
|
||||
def __init__(self):
|
||||
pass
|
||||
# 发票类型字典
|
||||
typeDict = {
|
||||
"增值税专用发票": 1,
|
||||
"增值税普通发票": 4,
|
||||
"增值税普通发票(电子)": 10,
|
||||
"增值税普通发票(卷式)": 11,
|
||||
"增值税普通发票(通行费)": 14,
|
||||
"增值税电子专用发票": 20,
|
||||
"数电发票(增值税专用发票)": 31,
|
||||
"数电发票(增值税普通发票)": 32,
|
||||
"数电发票(纸质专用发票)": 85,
|
||||
"数电发票(纸质普通发票)": 86,
|
||||
}
|
||||
@staticmethod
|
||||
def create_client() -> ocr_api20210707Client:
|
||||
"""
|
||||
使用凭据初始化账号Client
|
||||
@return: Client
|
||||
@throws Exception
|
||||
"""
|
||||
# 工程代码建议使用更安全的无AK方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378659.html。
|
||||
|
||||
# credential = CredentialClient()
|
||||
# config = open_api_models.Config(
|
||||
# credential=credential
|
||||
# )
|
||||
config = Config(
|
||||
type='access_key',
|
||||
access_key_id='LTAI5tHvCWb5uHZ9Q7iUJTLp',
|
||||
access_key_secret='zKAL7WqvnJ0lN0GHsvJptAGRiuCOfM'
|
||||
)
|
||||
config.endpoint = f'ocr-api.cn-hangzhou.aliyuncs.com'
|
||||
cred = CredentialClient(config)
|
||||
cred.get_credential()
|
||||
nconfig = open_api_models.Config(
|
||||
credential=cred
|
||||
)
|
||||
# Endpoint 请参考 https://api.aliyun.com/product/ocr-api
|
||||
|
||||
nconfig.endpoint = f'ocr-api.cn-hangzhou.aliyuncs.com'
|
||||
return ocr_api20210707Client(nconfig)
|
||||
# 发票验证
|
||||
@staticmethod
|
||||
def verify(data: dict, file_path: str,reverify: bool = False):
|
||||
dataservice = DataService()
|
||||
# 复验
|
||||
if not reverify:
|
||||
data1 = dataservice.get_verify_log(file_path=file_path)
|
||||
print(data1)
|
||||
if data1 is not None and int(data1.get('inspectionAmount')) > 0:
|
||||
return data1
|
||||
client = Service.create_client()
|
||||
type = Service.typeDict.get(data.get('invoiceType'))
|
||||
sum = data.get('totalAmount')
|
||||
if type == 1 or type == 20:
|
||||
sum = data.get('totalAmountWithoutTax')
|
||||
if data.get('checkCode') is not None:
|
||||
verify_code = data.get('checkCode')[-6:]
|
||||
else:
|
||||
verify_code = None
|
||||
date = data.get('invoiceDate').replace('年', "").replace('月', "").replace('日', "")
|
||||
verify_vatinvoice_request = ocr_api_20210707_models.VerifyVATInvoiceRequest(
|
||||
invoice_no= data.get('invoiceNumber'),
|
||||
invoice_code= data.get('invoiceCode'),
|
||||
invoice_date= date,
|
||||
invoice_sum= sum,
|
||||
invoice_kind= type,
|
||||
verify_code= verify_code,
|
||||
)
|
||||
runtime = util_models.RuntimeOptions()
|
||||
try:
|
||||
resp = client.verify_vatinvoice_with_options(verify_vatinvoice_request, runtime)
|
||||
ConsoleClient.log(UtilClient.to_jsonstring(resp))
|
||||
respjson = json.loads(UtilClient.to_jsonstring(resp.body.data))
|
||||
code = respjson.get('code')
|
||||
if code != '001':
|
||||
return {'cyjgxx': respjson.get('msg'), 'inspectionAmount': -1,'status': 'fail'}
|
||||
resdata = respjson.get('data')
|
||||
if resdata is None:
|
||||
resdata = {'cyjgxx': "未查询到发票信息", 'inspectionAmount': -1,'status': 'fail'}
|
||||
else:
|
||||
resdata.update({'status': 'success'})
|
||||
resdata['status'] = 'success'
|
||||
dataservice.insert_verify_log(resdata.get('inspectionAmount'), resdata.get('cyjgxx'), file_path)
|
||||
dataservice.update_invoice_status(data.get('id'), resdata.get('status'), resdata.get('inspectionAmount'))
|
||||
return resdata
|
||||
except Exception as error:
|
||||
# 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||
# 错误 message
|
||||
print(error)
|
||||
# 诊断地址
|
||||
print(error.data.get("Recommend"))
|
||||
UtilClient.assert_as_string(error.message)
|
||||
# 发票识别
|
||||
@staticmethod
|
||||
def recognize(file_path: str):
|
||||
client = Service.create_client()
|
||||
dataservice = DataService()
|
||||
body_stream = StreamClient.read_from_file_path(file_path)
|
||||
recognize_mixed_invoices_request = ocr_api_20210707_models.RecognizeMixedInvoicesRequest(
|
||||
body=body_stream
|
||||
)
|
||||
runtime = util_models.RuntimeOptions()
|
||||
try:
|
||||
# 复制代码运行请自行打印 API 的返回值
|
||||
res = client.recognize_mixed_invoices_with_options(recognize_mixed_invoices_request, runtime)
|
||||
print(UtilClient.to_jsonstring(res.body.data))
|
||||
data = json.loads(UtilClient.to_jsonstring(res.body.data)).get('subMsgs')[0].get('result').get('data')
|
||||
data_id = dataservice.insert_invoice_log(data, file_path)
|
||||
data.update({'id': data_id})
|
||||
return data
|
||||
except Exception as error:
|
||||
# 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||
# 错误 message
|
||||
print(error.message)
|
||||
# 诊断地址
|
||||
print(error.data.get("Recommend"))
|
||||
UtilClient.assert_as_string(error.message)
|
||||
# 发票验证记录
|
||||
@staticmethod
|
||||
def get_verify_log_list(file_path):
|
||||
dataservice = DataService()
|
||||
return dataservice.get_verify_log_list(file_path)
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
:root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}h1{font-size:3.2em;line-height:1.1}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}.card{padding:2em}#app{width:100%;height:100%;margin:0 auto;padding:2rem;text-align:center}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}.pdf[data-v-9e6af8d9]{width:100%;height:100%}.container[data-v-070d63cc]{margin-top:50px;height:100%}.pdf[data-v-5e9f78dd]{width:100%;height:100%}.ant-steps[data-v-85ed0412]{padding:0 20px 20px}.table-operations[data-v-31c060b8]{margin-bottom:16px;text-align:right}.table-operations>button[data-v-31c060b8]{margin-right:8px}#components-layout-demo-top-side .logo[data-v-274d3151]{float:left;height:31px;margin:16px 24px 16px 0;background:#ffffff4d}.ant-row-rtl #components-layout-demo-top-side .logo[data-v-274d3151]{float:right;margin:16px 0 16px 24px}.container[data-v-274d3151]{height:100%}html,body{width:100%;height:100%}input::-ms-clear,input::-ms-reveal{display:none}*,*:before,*:after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:rgba(0,0,0,0)}@-ms-viewport{width:device-width}body{margin:0}[tabindex="-1"]:focus{outline:none}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5em;font-weight:500}p{margin-top:0;margin-bottom:1em}abbr[title],abbr[data-original-title]{-webkit-text-decoration:underline dotted;text-decoration:underline;text-decoration:underline dotted;border-bottom:0;cursor:help}address{margin-bottom:1em;font-style:normal;line-height:inherit}input[type=text],input[type=password],input[type=number],textarea{-webkit-appearance:none}ol,ul,dl{margin-top:0;margin-bottom:1em}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:500}dd{margin-bottom:.5em;margin-left:0}blockquote{margin:0 0 1em}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}pre,code,kbd,samp{font-size:1em;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace}pre{margin-top:0;margin-bottom:1em;overflow:auto}figure{margin:0 0 1em}img{vertical-align:middle;border-style:none}a,area,button,[role=button],input:not([type=range]),label,select,summary,textarea{touch-action:manipulation}table{border-collapse:collapse}caption{padding-top:.75em;padding-bottom:.3em;text-align:left;caption-side:bottom}input,button,select,optgroup,textarea{margin:0;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}button,html [type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;margin:0;padding:0;border:0}legend{display:block;width:100%;max-width:100%;margin-bottom:.5em;padding:0;color:inherit;font-size:1.5em;line-height:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item}template{display:none}[hidden]{display:none!important}mark{padding:.2em;background-color:#feffe6}
|
||||
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue + TS</title>
|
||||
<script type="module" crossorigin src="/assets/index-CxFq9mgc.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-T_5mvQco.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
@@ -0,0 +1 @@
|
||||
__version__ = "1.0.0"
|
||||
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# This file is auto-generated, don't edit it. Thanks.
|
||||
import os
|
||||
import sys
|
||||
|
||||
from typing import List
|
||||
|
||||
from alibabacloud_ocr_api20210707.client import Client as ocr_api20210707Client
|
||||
from alibabacloud_credentials.client import Client as CredentialClient
|
||||
from alibabacloud_tea_openapi import models as open_api_models
|
||||
from alibabacloud_ocr_api20210707 import models as ocr_api_20210707_models
|
||||
from alibabacloud_tea_util import models as util_models
|
||||
from alibabacloud_tea_console.client import Client as ConsoleClient
|
||||
from alibabacloud_tea_util.client import Client as UtilClient
|
||||
|
||||
|
||||
class Sample:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def create_client() -> ocr_api20210707Client:
|
||||
"""
|
||||
使用凭据初始化账号Client
|
||||
@return: Client
|
||||
@throws Exception
|
||||
"""
|
||||
# 工程代码建议使用更安全的无AK方式,凭据配置方式请参见:https://help.aliyun.com/document_detail/378659.html。
|
||||
credential = CredentialClient()
|
||||
config = open_api_models.Config(
|
||||
credential=credential
|
||||
)
|
||||
# Endpoint 请参考 https://api.aliyun.com/product/ocr-api
|
||||
config.endpoint = f'ocr-api.cn-hangzhou.aliyuncs.com'
|
||||
return ocr_api20210707Client(config)
|
||||
|
||||
@staticmethod
|
||||
def main(
|
||||
args: List[str],
|
||||
) -> None:
|
||||
client = Sample.create_client()
|
||||
verify_vatinvoice_request = ocr_api_20210707_models.VerifyVATInvoiceRequest(
|
||||
invoice_no='25617000000124363744',
|
||||
invoice_date='20250620'
|
||||
)
|
||||
runtime = util_models.RuntimeOptions()
|
||||
try:
|
||||
resp = client.verify_vatinvoice_with_options(verify_vatinvoice_request, runtime)
|
||||
ConsoleClient.log(UtilClient.to_jsonstring(resp))
|
||||
except Exception as error:
|
||||
# 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||
# 错误 message
|
||||
print(error.message)
|
||||
# 诊断地址
|
||||
print(error.data.get("Recommend"))
|
||||
UtilClient.assert_as_string(error.message)
|
||||
|
||||
@staticmethod
|
||||
async def main_async(
|
||||
args: List[str],
|
||||
) -> None:
|
||||
client = Sample.create_client()
|
||||
verify_vatinvoice_request = ocr_api_20210707_models.VerifyVATInvoiceRequest(
|
||||
invoice_no='25617000000124363744',
|
||||
invoice_date='20250620'
|
||||
)
|
||||
runtime = util_models.RuntimeOptions()
|
||||
try:
|
||||
resp = await client.verify_vatinvoice_with_options_async(verify_vatinvoice_request, runtime)
|
||||
ConsoleClient.log(UtilClient.to_jsonstring(resp))
|
||||
except Exception as error:
|
||||
# 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||
# 错误 message
|
||||
print(error.message)
|
||||
# 诊断地址
|
||||
print(error.data.get("Recommend"))
|
||||
UtilClient.assert_as_string(error.message)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Sample.main(sys.argv[1:])
|
||||
@@ -0,0 +1,10 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
python-app:
|
||||
build: .
|
||||
container_name: python-app
|
||||
volumes:
|
||||
- ./:/app
|
||||
- ./data:/data
|
||||
ports:
|
||||
- "8555:5555" # 如果需要暴露 Flask 端口
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
FROM python:latest
|
||||
|
||||
WORKDIR /app
|
||||
RUN git clone http://192.168.1.68:8418/jayus/VerifVATInvoice.git .
|
||||
COPY . .
|
||||
RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple
|
||||
|
||||
RUN python setup.py install
|
||||
WORKDIR /app/alibabacloud_sample
|
||||
CMD ["python", "main.py"]
|
||||
@@ -0,0 +1,11 @@
|
||||
alibabacloud_credentials==1.0.2
|
||||
alibabacloud_darabonba_stream==0.0.2
|
||||
alibabacloud_ocr_api20210707==3.1.3
|
||||
alibabacloud_tea_console==0.0.1
|
||||
alibabacloud_tea_openapi==0.4.1
|
||||
alibabacloud_tea_util==0.3.13
|
||||
Flask==3.1.1
|
||||
flask_cors==6.0.1
|
||||
python_dateutil==2.9.0.post0
|
||||
Werkzeug==3.1.3
|
||||
setuptools
|
||||
@@ -0,0 +1,80 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
"""
|
||||
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
"""
|
||||
setup module for alibabacloud_sample.
|
||||
|
||||
Created on 31/07/2025
|
||||
|
||||
@author:
|
||||
"""
|
||||
|
||||
PACKAGE = "alibabacloud_sample"
|
||||
NAME = "alibabacloud_sample" or "alibabacloud-package"
|
||||
DESCRIPTION = "Alibaba Cloud SDK Code Sample Library for Python"
|
||||
AUTHOR = ""
|
||||
AUTHOR_EMAIL = ""
|
||||
URL = "https://github.com/aliyun/alibabacloud-sdk"
|
||||
VERSION = __import__(PACKAGE).__version__
|
||||
REQUIRES = [
|
||||
"alibabacloud_ocr-api20210707>=3.1.3, <4.0.0",
|
||||
"alibabacloud_tea_openapi>=0.3.15, <1.0.0",
|
||||
"alibabacloud_tea_console>=0.0.1, <1.0.0",
|
||||
"alibabacloud_tea_util>=0.3.13, <1.0.0",
|
||||
"alibabacloud_credentials>=1.0.2, <2.0.0", 'flask'
|
||||
]
|
||||
|
||||
LONG_DESCRIPTION = ''
|
||||
if os.path.exists('./README.md'):
|
||||
with open("README.md", encoding='utf-8') as fp:
|
||||
LONG_DESCRIPTION = fp.read()
|
||||
|
||||
setup(
|
||||
name=NAME,
|
||||
version=VERSION,
|
||||
description=DESCRIPTION,
|
||||
long_description=LONG_DESCRIPTION,
|
||||
long_description_content_type='text/markdown',
|
||||
author=AUTHOR,
|
||||
author_email=AUTHOR_EMAIL,
|
||||
license="Apache License 2.0",
|
||||
url=URL,
|
||||
keywords=["alibabacloud","sample"],
|
||||
packages=find_packages(exclude=["tests*"]),
|
||||
include_package_data=True,
|
||||
platforms="any",
|
||||
install_requires=REQUIRES,
|
||||
python_requires=">=3.6",
|
||||
classifiers=(
|
||||
"Development Status :: 4 - Beta",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
"Topic :: Software Development"
|
||||
)
|
||||
)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "webapp",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/dateutil": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/dateutil/-/dateutil-0.1.0.tgz",
|
||||
"integrity": "sha512-bI8Lm50mageQER89AvYr+bAxXn4MCAkjSg3M58QY2evrIypUk2aCQcMZ86f31tjmt2ceMrPkrHkhItYqOJsObw==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2010 Borgar Þorsteinsson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
# Dateutil
|
||||
|
||||
Dateutil is a modest collection of utility methods for manipulating dates. It works both in Node.JS and in browsers. It's goal is to provide extendable mechanisms for parsing and formatting dates, as well as other convenient date methods.
|
||||
|
||||
Currently, the library supports formatting of nearly full spec of PHP style dates (minus the really useless bits), and parses about the full range of ISO 8901 formats. It supports translations (for formatter) but no timezones.
|
||||
|
||||
## Dateutil methods
|
||||
|
||||
### dateutil.date( [year], [month], [day], [hour], [min], [sec], [ms] )
|
||||
|
||||
Function returns a new *Date* instance set to the expected date. If no arguments are given, then it returns the current date. When any arguments are given they are handled in much the same way the native date constructor does, except the first argument is always treated as a year.
|
||||
|
||||
It is safe to pass strings as arguments.
|
||||
|
||||
**Please note:** Months are zero based in set just as they are in native *Date* construction.
|
||||
|
||||
|
||||
|
||||
### dateutil.isLeapYear( date / year )
|
||||
|
||||
Function accepts a *Date*, or a year, and will return true if the year is a leap year or false if not.
|
||||
|
||||
dateutil.isLeapYear( 1468 ) == true
|
||||
|
||||
|
||||
### dateutil.daysInMonth( date )
|
||||
|
||||
Function accepts a *Date* and returns the number of days in it's month.
|
||||
|
||||
dateutil.daysInMonth( new Date(1920, 3, 1) ) == 30
|
||||
|
||||
|
||||
### dateutil.isocalendar( date )
|
||||
|
||||
Function accepts a *Date* and returns a three value array containing the ISO 8901 year, week, and day (of the week), respectively.
|
||||
|
||||
dateutil.isocalendar( new Date(2010, 0, 3) ) == [2009, 53, 7]
|
||||
|
||||
|
||||
### dateutil.set( date, values )
|
||||
|
||||
Function accepts a *Date* and an object of values. The function returns the input date (same instance) having set the units specified in the values collection.
|
||||
|
||||
var mydate = new Date( 2000, 9, 16, 10, 45, 12 );
|
||||
dateutil.set( mydate, { hour: 0, minute: 0, second: 0 });
|
||||
|
||||
result: mydate == new Date( 2000, 9, 16, 0, 0, 0 );
|
||||
|
||||
The function will accept all or any of these keys:
|
||||
`year`, `month`, `day`, `hour`, `minute`, `second`, `millisecond`. and their plural forms (`years`, `minutes`, etc. ); and these shorthand variations: `yr` (year), `mn` (month), `day` (day), `hr` (hour), `min` (minute), `sec` (second), `ms` (millisecond).
|
||||
|
||||
**Please note:** Months are zero based in set just as they are in native *Date* construction.
|
||||
|
||||
This function tries to avoid rollover gotchas that can occur when using native members to set date values. A simplified example of this:
|
||||
|
||||
var d = new Date( 1999, 1, 20 );
|
||||
d.setUTCDate( 30 ); // Tue Mar 02 1999 00:00:00 GMT+0000 (GMT)
|
||||
d.setUTCMonth( 0 ); // Sat Jan 02 1999 00:00:00 GMT+0000 (GMT)
|
||||
|
||||
The `dateutil.set` function is takes steps to avoid the problem:
|
||||
|
||||
var d = new Date( 1999, 1, 20 );
|
||||
dateutil.set(d, {
|
||||
'date': 30,
|
||||
'month': 0
|
||||
}); // Sat Jan 30 1999 00:00:00 GMT+0000 (GMT)
|
||||
|
||||
var d = new Date( 1999, 1, 20 );
|
||||
dateutil.set(d, {
|
||||
'month': 0,
|
||||
'date': 30
|
||||
}); // Sat Jan 30 1999 00:00:00 GMT+0000 (GMT)
|
||||
|
||||
If you want to be sure you get the correct date then you need to use the `dateutil.date`
|
||||
|
||||
|
||||
### dateutil.parse( string )
|
||||
|
||||
Function accepts a string (representing a date) and will output a *Date* based on parsing the string.
|
||||
|
||||
dateutil.parse('2005-01-01') == new Date(2005, 0, 1)
|
||||
|
||||
The function will aways return a *Date* instance, valid or not. Internally it falls back to native parser if it fails to recognize the format.
|
||||
|
||||
A mostly complete list of recognized formats:
|
||||
|
||||
* yyyy-mm-dd hh:mm:ss.ffff+0000 — *(timezone is currently ignored)*
|
||||
* yyyy-mm-dd hh:mm:ss.ffffZ
|
||||
* yyyy-mm-dd hh:mm:ss.ffff
|
||||
* yyyy-mm-dd hh:mm
|
||||
* yyyy-mm-ddThh:mm:ss.ffff+0000 — *(timezone is currently ignored)*
|
||||
* yyyy-mm-ddThh:mm:ss.ffffZ
|
||||
* yyyy-mm-ddThh:mm:ss.ffff
|
||||
* yyyy-mm-ddThh:mm
|
||||
* yyyy-mm-dd
|
||||
* yyyy-mmdd
|
||||
* yyyymmdd
|
||||
* yyyy-ddd — *year with day-of-the-year*
|
||||
* yyyy-Www-d
|
||||
* yyyy-Wwwd
|
||||
* yyyy-Www
|
||||
* yyyyWww-d
|
||||
* yyyyWwwd
|
||||
* yyyyWww
|
||||
* yyyy-Qq — *year with quarter*
|
||||
* yyyyQq
|
||||
* yyyy-mm
|
||||
* yyyy/mm
|
||||
* yyyy
|
||||
|
||||
... In addition to the formats JavaScript can natively parse.
|
||||
|
||||
|
||||
#### Adding a custom parser
|
||||
|
||||
You may add your own parser by assigning them into `dateutil._parsers`. A parser is simply an object with two members:
|
||||
|
||||
* `test`: a regular expression used to test if this date can be parsed by the parser. Keep these simple and avoid capture groups, or suffer the slowdown cost.
|
||||
* `parse`: a function that handles turning the string into a `Date` instance.
|
||||
|
||||
An contrived example:
|
||||
|
||||
// year + excel week
|
||||
dateutil._parsers['year_and_excel_week'] = {
|
||||
test: /^\d{4}_[0-5]\d$/,
|
||||
parse: function ( str ) {
|
||||
var bits = str.split('_');
|
||||
var year = parseInt( bits[0], 10 );
|
||||
var dofy = parseInt( bits[1], 10 );
|
||||
return new Date( year, 0, ((dofy - 1) * 7) + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// usage:
|
||||
dateutil.parse('2002_31') == new Date(2002, 6, 30)
|
||||
|
||||
|
||||
### dateutil.format( date, format_string, [language] )
|
||||
|
||||
Function accepts a *Date, a *PHP* style [format string][1], and an optional language identifyer, and will return a formatted date string. Refer to the [PHP docs][1] for the full spec of available format characters.
|
||||
|
||||
var mydate = new Date( 2000, 9, 16, 10, 45, 12 );
|
||||
dateutil.format( mydate, 'jS F Y' ) == "16th October 2000"
|
||||
|
||||
The `B`, `Z`, and `I` characters have been purposely omitted, and the following new characters added:
|
||||
|
||||
* `q` — quarter of the year
|
||||
|
||||
The third optional parameter will get passed through the formatting system until it reaches
|
||||
|
||||
|
||||
The format method is smart about being assigned to the `Date.prototype` object and will work as expected:
|
||||
|
||||
Date.prototype.format = dateutil.format;
|
||||
new Date( 1975, 9, 16 ).format( 'Y-m-d' ) == "1975-10-16"
|
||||
|
||||
|
||||
#### Adding a custom formatter
|
||||
|
||||
You may add your own formatter by assigning them into `dateutil._formats`. A parser is simply a function that takes a *Date* parameter, a language parameter, and returns a string.
|
||||
|
||||
An example that adds *Swatch internet time* formatter:
|
||||
|
||||
// Swatch Internet time
|
||||
dateutil._formats['B'] = function (d) {
|
||||
var mo = 0; // This should really be: isDST * 60 + timeZoneOffsetInMinutes
|
||||
return Math.round( ( d.getUTCHours() * 3600 +
|
||||
( ( d.getUTCMinutes() - mo + 60 ) * 60 ) +
|
||||
d.getUTCSeconds() ) * 1000 / 86400 ) % 1000;
|
||||
};
|
||||
|
||||
var mydate = new Date( 1961, 0, 3, 1, 51, 10, 1 );
|
||||
dateutil.format( mydate, 'B' ) == '119'
|
||||
|
||||
If you call `dateutil._`, or recursively call formatting or other specific formatting functions for delegating work, it is important that you keep passing the second parameter so that a specified language is perserved.
|
||||
|
||||
An example that adds upper case 4 letter month name:
|
||||
|
||||
dateutil._formats['R'] = function (d,l) {
|
||||
var mont = this['F']( d, l ); // fetch translated month in "l" language
|
||||
return mont.substr( 0, 4 ).toUpperCase();
|
||||
};
|
||||
|
||||
|
||||
### dateutil.today()
|
||||
|
||||
Function returns a new *Date* instance set to the current day.
|
||||
|
||||
|
||||
### dateutil.now()
|
||||
|
||||
Function returns a new *Date* instance set to the current moment. Uses native implementation if it exists.
|
||||
|
||||
|
||||
### dateutil._( string, [language] )
|
||||
|
||||
A string translation function. This function looks for the string argument as a key in `dateutil.lang[ language ]` and uses the the value if it exists, otherwise it falls back on the string argument.
|
||||
|
||||
You can feed the system translation strings and use it, or alternatively, if you already have another translation system then you can overwrite this function with your own translation function.
|
||||
|
||||
In order to use the built in system you would add translated strings like in this example (Icelandic language):
|
||||
|
||||
dateutil.lang.is = {
|
||||
'January': 'janúar',
|
||||
'February': 'febrúar',
|
||||
'March': 'mars',
|
||||
'April': 'apríl',
|
||||
'May': 'maí',
|
||||
'June': 'júní',
|
||||
// and so on ...
|
||||
};
|
||||
|
||||
You should provide translations for the following strings:
|
||||
|
||||
* January February March April May June July August September October November December
|
||||
* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
|
||||
* Sunday Monday Tuesday Wednesday Thursday Friday Saturday
|
||||
* Sun Mon Tue Wed Thu Fri Sat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[1]: http://php.net/manual/en/function.date.php
|
||||
+398
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
* Dateutil
|
||||
* - provides formatting, parsing and other utility functions for dates.
|
||||
*
|
||||
* Copyright (c) 2009 Borgar Þorsteinsson
|
||||
* Licensed under the terms of the MIT (LICENSE.txt) software license.
|
||||
*
|
||||
*/
|
||||
/*jslint laxbreak: true, undef: true *//*global module */
|
||||
(function(__global__){
|
||||
|
||||
var SECOND_SIZE = 1000;
|
||||
var MINUTE_SIZE = SECOND_SIZE * 60;
|
||||
var HOUR_SIZE = MINUTE_SIZE * 60;
|
||||
var DAY_SIZE = HOUR_SIZE * 24;
|
||||
var WEEK_SIZE = DAY_SIZE * 7;
|
||||
var MONTH_SIZE = DAY_SIZE * 30.436875; // average month size
|
||||
var YEAR_SIZE = DAY_SIZE * 365.2425; // average year size
|
||||
|
||||
var _gl = __global__.lang = { 'en': {} };
|
||||
var _m = 'January February March April May June July August September October November December'.split(' ');
|
||||
var _d = 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' ');
|
||||
|
||||
var method_size = {
|
||||
'FullYear': 6, 'Month': 5, 'Date': 4, 'Hours': 3,
|
||||
'Minutes': 2, 'Seconds': 1, 'Milliseconds': 0
|
||||
};
|
||||
var method_map = {
|
||||
'yr': 'FullYear',
|
||||
'year': 'FullYear',
|
||||
'years': 'FullYear',
|
||||
'mn': 'Month',
|
||||
'month': 'Month',
|
||||
'months': 'Month',
|
||||
'day': 'Date',
|
||||
'days': 'Date',
|
||||
'date': 'Date',
|
||||
'hr': 'Hours',
|
||||
'hour': 'Hours',
|
||||
'hours': 'Hours',
|
||||
'min': 'Minutes',
|
||||
'minute': 'Minutes',
|
||||
'minutes': 'Minutes',
|
||||
'sec': 'Seconds',
|
||||
'second': 'Seconds',
|
||||
'seconds': 'Seconds',
|
||||
'ms': 'Milliseconds',
|
||||
'millisecond': 'Milliseconds',
|
||||
'milliseconds': 'Milliseconds'
|
||||
};
|
||||
|
||||
// *****************************************
|
||||
// *** *** *** formats & parsers *** *** ***
|
||||
// *****************************************
|
||||
|
||||
var date_parsers = __global__._parsers = {
|
||||
|
||||
// year + month + day + time
|
||||
// -- currently doesn't really support fractions on anything other than seconds >> FIXME
|
||||
// -- does not support timezones other than Zulu
|
||||
date_and_time: {
|
||||
test: /^[+\-]?\d{4,6}(?:(?:\-\d\d){1,2}|\d{4})[T ](?:\d\d)(?::?\d\d){0,2}(?:[\.,]\d+)?(?:Z|[+\-]\d\d(:?\d\d)?)?$/,
|
||||
size: 1,
|
||||
parse: function ( str ) {
|
||||
var b = str.split( /[T ]/ );
|
||||
var date = date_parsers.date.parse( b[0] );
|
||||
var m = b[1].replace( /:/g, '' )
|
||||
.match( /^(\d\d)(\d\d)?(\d\d)?(?:[.,](\d+))?([+\-](?:\d\d){1,2})?/ );
|
||||
// TODO: timezone (I have no need for this feature yet)
|
||||
// if ( m[5] ) { var zone = m[5] || '0000'; }
|
||||
var fs = 0, t = date.getTime() +
|
||||
parseInt( m[1], 10 ) * HOUR_SIZE +
|
||||
parseInt( m[2] || '0', 10 ) * MINUTE_SIZE +
|
||||
parseInt( m[3] || '0', 10 ) * SECOND_SIZE;
|
||||
if ( m[3] ) { fs = SECOND_SIZE; }
|
||||
else if ( m[2] ) { fs = MINUTE_SIZE; }
|
||||
else if ( m[1] ) { fs = HOUR_SIZE; }
|
||||
t += parseFloat( '0.' + ( m[4] || '0' ) ) * fs;
|
||||
date.setTime( t );
|
||||
date.size = 0;
|
||||
return date;
|
||||
}
|
||||
},
|
||||
|
||||
// year + month + day
|
||||
date: {
|
||||
test: /^[+\-]?\d{4,6}(?:\-\d\d\-\d\d|-?\d\d\d\d)$/,
|
||||
size: DAY_SIZE,
|
||||
parse: function ( str ) {
|
||||
var s = str.replace( /\D/g, '' ),
|
||||
d = __global__.date(
|
||||
s.substring( 0, s.length -4 ),
|
||||
+s.substr( s.length -4, 2 ) - 1,
|
||||
s.substr( s.length -2 )
|
||||
);
|
||||
d.size = DAY_SIZE;
|
||||
return d;
|
||||
}
|
||||
},
|
||||
|
||||
// year + month
|
||||
year_and_month: {
|
||||
test: /^[+\-]?\d{4,6}[\/\-](?:0[1-9]|1[012])$/,
|
||||
size: MONTH_SIZE,
|
||||
parse: function ( str ) {
|
||||
var b = str.split( /[\/\-]/ );
|
||||
var d = __global__.date( b[0], +b[1] - 1, 1 );
|
||||
d.size = __global__.daysInMonth( d ) * DAY_SIZE;
|
||||
return d;
|
||||
}
|
||||
},
|
||||
|
||||
// year
|
||||
year: {
|
||||
test: /^[+\-]?\d{4,6}$/,
|
||||
size: YEAR_SIZE,
|
||||
parse: function ( str ) {
|
||||
var d = __global__.date( str, 0, 1 );
|
||||
d.size = DAY_SIZE * ( __global__.isLeapYear( d ) ? 366 : 365 );
|
||||
return d;
|
||||
}
|
||||
},
|
||||
|
||||
// year + iso week + [day]
|
||||
year_and_week: {
|
||||
test: /^[+\-]?\d{4,6}\-?[Ww]\d\d(?:\-?\d)?$/,
|
||||
size: WEEK_SIZE,
|
||||
parse: function ( str ) {
|
||||
var s = str.toLowerCase().replace( /[^w\d]/g, '' ).split('w');
|
||||
var d = __global__.date( s[0], 0, 3 ); // Jan 3
|
||||
d.setUTCDate( 3 - d.getUTCDay() +
|
||||
( parseInt( s[1].substr( 0, 2 ), 10 ) - 1 ) * 7 +
|
||||
parseInt( s[1].substr( 2, 1 ) || '1', 10 ) );
|
||||
d.size = WEEK_SIZE;
|
||||
return d;
|
||||
}
|
||||
},
|
||||
|
||||
// year + day-of-year
|
||||
// -- we don't allow the short form yyyyddd because of ambiguity with yyyymmdd
|
||||
// -- 5 letter years would clash with cal-dates: yyyyyddd ~ yyyymmdd
|
||||
year_and_ordinal: {
|
||||
test: /^[+\-]?\d{4,6}\-[0-3]\d\d$/,
|
||||
size: DAY_SIZE,
|
||||
parse: function ( str ) {
|
||||
var d = new Date(0);
|
||||
d.setUTCFullYear( parseInt( str.substr(0,str.length-4), 10 ) );
|
||||
d.setDate( parseInt( str.substr(str.length-3), 10 ) );
|
||||
d.size = DAY_SIZE;
|
||||
return d;
|
||||
}
|
||||
},
|
||||
|
||||
// year + quarter
|
||||
year_and_quarter: {
|
||||
test: /^[+\-]?\d{4,6}\-?[Qq][1-4]$/,
|
||||
size: YEAR_SIZE / 4,
|
||||
parse: function ( str ) {
|
||||
var b = str.split(/\-?[Qq]/),
|
||||
d = __global__.date( b[0], ( parseInt( b[1], 10 ) - 1 ) * 3 );
|
||||
d.size = DAY_SIZE;
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var date_formatters = __global__._formats = {
|
||||
// Lowercase Ante meridiem and Post meridiem
|
||||
a: function (d) { return d.getUTCHours() >= 12 ? 'pm' : 'am'; },
|
||||
// Uppercase Ante meridiem and Post meridiem
|
||||
A: function (d) { return d.getUTCHours() >= 12 ? 'PM' : 'AM'; },
|
||||
// ISO 8601 date
|
||||
c: function (d,l) { return __global__.format( d, 'Y-m-d\\TH:i:s.', l ) + __global__.pad( d.getUTCMilliseconds(), 3 ) + 'Z'; },
|
||||
// Day of the month, 2 digits with leading zeros
|
||||
d: function (d) { return __global__.pad( d.getUTCDate() ); },
|
||||
// A textual representation of a day, three letters
|
||||
D: function (d,l) { return __global__._( _d[ d.getUTCDay() ].substr( 0, 3 ), l ); },
|
||||
// Time zone identifier
|
||||
e: function (d) { return 'UTC'; },
|
||||
// A full textual representation of a month
|
||||
F: function (d,l) { return __global__._( _m[ d.getUTCMonth() ], l ); },
|
||||
// 12-hour format of an hour without leading zeros
|
||||
g: function (d) { return d.getUTCHours() % 12 || 12; },
|
||||
// 24-hour format of an hour without leading zeros
|
||||
G: function (d) { return d.getUTCHours(); },
|
||||
// 12-hour format of an hour with leading zeros
|
||||
h: function (d) { return __global__.pad( d.getUTCHours() % 12 || 12 ); },
|
||||
// 24-hour format of an hour with leading zeros
|
||||
H: function (d) { return __global__.pad( d.getUTCHours() ); },
|
||||
// Minutes with leading zeros
|
||||
i: function (d) { return __global__.pad( d.getUTCMinutes() ); },
|
||||
// Day of the month without leading zeros
|
||||
j: function (d) { return d.getUTCDate(); },
|
||||
// A full textual representation of the day of the week
|
||||
l: function (d,l) { return __global__._( _d[ d.getUTCDay() ], l ); },
|
||||
// Whether it's a leap year (0 = yes, 1 = no)
|
||||
L: function (d) { return __global__.isLeapYear( d ) * 1; },
|
||||
// Numeric representation of a month, with leading zeros
|
||||
m: function (d) { return __global__.pad( d.getUTCMonth() + 1 ); },
|
||||
// A short textual representation of a month, three letters
|
||||
M: function (d,l) { return __global__._( _m[ d.getUTCMonth() ].substr( 0, 3 ), l ); },
|
||||
// Numeric representation of a month, without leading zeros
|
||||
n: function (d) { return d.getUTCMonth() + 1; },
|
||||
// ISO-8601 numeric representation of the day of the week
|
||||
N: function (d) { return d.getUTCDay() || 7; },
|
||||
// ISO-8601 year number
|
||||
o: function (d) { return __global__.pad( __global__.isocalendar(d)[0], 4 ); },
|
||||
// Time zone designator
|
||||
O: function (d) { return '+0000'; },
|
||||
// Time zone difference
|
||||
P: function (d) { return '+00:00'; },
|
||||
// Quarter of the year
|
||||
q: function (d) { return ~~( d.getUTCMonth() / 3 ) + 1; },
|
||||
// RFC 2822 formatted date
|
||||
r: function (d,l) { return __global__.format( d, 'D, d M Y H:i:s O', l ); },
|
||||
// Seconds, with leading zeros
|
||||
s: function (d) { return __global__.pad( d.getUTCSeconds() ); },
|
||||
// English ordinal suffix for the day of the month, 2 characters
|
||||
S: function (d) {
|
||||
var a = d.getUTCDate() % 10, b = d.getUTCDate() % 100;
|
||||
return (a === 1) && (b !== 11) && 'st' ||
|
||||
(a === 2) && (b !== 12) && 'nd' ||
|
||||
(a === 3) && (b !== 13) && 'rd' || 'th';
|
||||
},
|
||||
// Number of days in the given month
|
||||
t: function (d) { return __global__.daysInMonth( d ); },
|
||||
// Time zone abbreviation
|
||||
T: function (d) { return 'UTC'; },
|
||||
// Microseconds
|
||||
u: function (d) { return __global__.pad( d.getUTCMilliseconds(), 6 ); },
|
||||
// Seconds since the Unix Epoch
|
||||
U: function (d) { return ~~( d / 1000 ); },
|
||||
// Numeric representation of the day of the week
|
||||
w: function (d) { return d.getUTCDay(); },
|
||||
// ISO-8601 week number of year, weeks starting on Monday
|
||||
W: function (d) { return __global__.pad( __global__.isocalendar(d)[1] ); },
|
||||
// A short numeric representation of a year, 2 digits
|
||||
y: function (d) { return (d.getUTCFullYear() + '').substr(2); },
|
||||
// A full numeric representation of a year, 4 digits
|
||||
Y: function (d) { return d.getUTCFullYear(); },
|
||||
// The day of the year (starting from 0)
|
||||
z: function (d) { return Math.floor( ( d - (new Date(Date.UTC(d.getUTCFullYear(), 0, 1))) ) / DAY_SIZE ); }
|
||||
};
|
||||
|
||||
// **************************************
|
||||
// *** *** *** module methods *** *** ***
|
||||
// **************************************
|
||||
|
||||
__global__.date = function ( y, m, d, h, n, s, ms ) {
|
||||
var ts = ( !arguments.length ) ? __global__.now()
|
||||
: Date.UTC( parseInt( y||0, 10 ), parseInt( m||0, 10 ), parseInt( d||1, 10 ),
|
||||
parseInt( h||0, 10 ), parseInt( n||0, 10 ), parseInt( s||0, 10 ),
|
||||
parseInt( ms||0, 10 ) );
|
||||
return new Date( ts );
|
||||
};
|
||||
|
||||
|
||||
// zero pad a string n to l places
|
||||
__global__.pad = function ( n, l ) {
|
||||
var s = __global__.pad.z;
|
||||
if ( !s ) { // This mess is here because JSlint breaks on new Array(999)
|
||||
var a = []; a[999] = '';
|
||||
s = __global__.pad.z = a.join('0');
|
||||
}
|
||||
s += n;
|
||||
return s.substring( s.length -( l || 2 ) );
|
||||
};
|
||||
|
||||
// is a given year a leap year
|
||||
__global__.isLeapYear = function ( y ) {
|
||||
if ( y instanceof Date ) { y = y.getUTCFullYear(); }
|
||||
return (( y % 4 === 0 ) && ( y % 100 !== 0 )) || ( y % 400 === 0 );
|
||||
};
|
||||
|
||||
|
||||
// return the number of days in a date's month
|
||||
__global__.daysInMonth = function ( dt ) {
|
||||
var m = dt.getUTCMonth();
|
||||
if ( m === 1 ) {
|
||||
return __global__.isLeapYear( dt ) ? 29 : 28;
|
||||
}
|
||||
return [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ][ m ];
|
||||
};
|
||||
|
||||
|
||||
// return a 3-tuple, (ISO year, ISO week number, ISO weekday).
|
||||
__global__.isocalendar = function ( dt ) {
|
||||
var d = dt.getUTCDay();
|
||||
var t = new Date( dt.valueOf() );
|
||||
t.setDate( t.getDate() - ((d + 6) % 7) + 3 );
|
||||
var iso_year = t.getUTCFullYear();
|
||||
var w = Math.floor( (t.getTime() - __global__.date(iso_year, 0, 1, -6)) / 86400000 );
|
||||
return [ iso_year, 1+Math.floor(w/7), d||7 ];
|
||||
};
|
||||
|
||||
|
||||
// Allow setting multiple properties at once using object notation:
|
||||
// `mydate.set({ hour: 8, minute: 12, second: 0 });`
|
||||
__global__.set = function ( dt, values ) {
|
||||
if ( typeof values === 'object' ) {
|
||||
var s = [], n, i;
|
||||
// step 1: collect a list of values to modify
|
||||
for ( var key in values ) {
|
||||
if ( key in method_map ) {
|
||||
n = method_map[ key ];
|
||||
s.push( [ values[ key ], n, method_size[ n ] ] );
|
||||
}
|
||||
}
|
||||
// step 2: order values by size
|
||||
s = s.sort(function ( a, b ) { return a[2] - b[2]; });
|
||||
// step 3: little endian value zeroing
|
||||
for (i=0; i<s.length; i++) {
|
||||
dt[ 'setUTC' + s[i][1] ]( s[i][1] === 'Date' ? 1 : 0 );
|
||||
}
|
||||
// step 4: big endian value setting
|
||||
for (i=s.length; i--; ) {
|
||||
dt[ 'setUTC' + s[i][1] ]( s[i][0] );
|
||||
}
|
||||
}
|
||||
return dt;
|
||||
};
|
||||
|
||||
|
||||
// parse a date
|
||||
__global__.parse = function ( str ) {
|
||||
var d;
|
||||
if ( typeof str !== 'string' ) {
|
||||
throw new Error( "dateutil parser can't parse non-strings." );
|
||||
}
|
||||
for ( var dtype in date_parsers ) {
|
||||
if ( date_parsers[dtype].test.test( str ) ) {
|
||||
d = date_parsers[ dtype ].parse( str );
|
||||
d.type = dtype;
|
||||
d.size = d.size || 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// default parser supports RFC and a few more, or returns an "invalid date"
|
||||
if ( !d ) {
|
||||
d = new Date( str );
|
||||
d.size = 0;
|
||||
d.type = 'unknown_date';
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
|
||||
// format a date to string
|
||||
__global__.format = function ( d, fmt, lang ) {
|
||||
if ( arguments.length === 1 && this instanceof Date ) {
|
||||
fmt = d;
|
||||
d = this;
|
||||
}
|
||||
else if ( !(d instanceof Date) ) {
|
||||
throw new Error('No date passed to format.');
|
||||
}
|
||||
for ( var r=[], c, l=fmt.length, i=0; i<l; i++ ) {
|
||||
c = fmt.charAt( i );
|
||||
// format characters
|
||||
if ( c !== '\\' ) {
|
||||
r.push( (c in date_formatters) ? date_formatters[ c ]( d, lang ) : c );
|
||||
}
|
||||
// escaped characters & unreconized characters
|
||||
else {
|
||||
c = i < fmt.length ? fmt.charAt( ++i ) : c;
|
||||
r.push( c );
|
||||
}
|
||||
}
|
||||
return r.join( '' );
|
||||
};
|
||||
|
||||
|
||||
// return a Date object for the current date (0 time)
|
||||
__global__.today = function () {
|
||||
return __global__.set( __global__.date(), {
|
||||
hour: 0, minute: 0, second: 0, millisecond: 0
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// return timestamp for the moment
|
||||
__global__.now = ( typeof Date.now === 'function' )
|
||||
? Date.now
|
||||
: function () { return +new Date(); };
|
||||
|
||||
|
||||
// translation hook
|
||||
__global__._ = function ( s, lang ) {
|
||||
var l = lang && __global__.lang[ lang ];
|
||||
return ( l && s in l ) ? l[s] : s;
|
||||
};
|
||||
|
||||
}(
|
||||
(typeof module !== 'undefined' && module.exports)
|
||||
? module.exports
|
||||
: (this.dateutil = {})
|
||||
));
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset=utf-8 />
|
||||
<title>dateutil tests</title>
|
||||
|
||||
<!-- links to qunit + jquery -->
|
||||
<link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" />
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
|
||||
<script src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></script>
|
||||
|
||||
<!-- JSON tests -->
|
||||
<script src="dateutil.js"></script>
|
||||
<script src="dateutil.testdates.js"></script>
|
||||
<script src="dateutil.test.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1 id="qunit-header">dateutil tests</h1>
|
||||
<h2 id="qunit-banner"></h2>
|
||||
<h2 id="qunit-userAgent"></h2>
|
||||
<ol id="qunit-tests"></ol>
|
||||
</body>
|
||||
</html>
|
||||
+396
@@ -0,0 +1,396 @@
|
||||
test('dateutil', function () {
|
||||
|
||||
ok( typeof dateutil === 'object', 'have dateutil' );
|
||||
|
||||
});
|
||||
|
||||
|
||||
test("dateutil.isLeapYear", function () {
|
||||
|
||||
// expect( 10 );
|
||||
ok( typeof dateutil.isLeapYear === 'function', 'dateutil.isLeapYear() is a function' );
|
||||
same( dateutil.isLeapYear(1468), true , 'dateutil.isLeapYear(1468)' );
|
||||
same( dateutil.isLeapYear(1600), true , 'dateutil.isLeapYear(1600)' );
|
||||
same( dateutil.isLeapYear(1852), true , 'dateutil.isLeapYear(1852)' );
|
||||
same( dateutil.isLeapYear(1920), true , 'dateutil.isLeapYear(1920)' );
|
||||
same( dateutil.isLeapYear(1968), true , 'dateutil.isLeapYear(1968)' );
|
||||
same( dateutil.isLeapYear(2000), true , 'dateutil.isLeapYear(2000)' );
|
||||
same( dateutil.isLeapYear(2008), true , 'dateutil.isLeapYear(2008)' );
|
||||
same( dateutil.isLeapYear(2400), true , 'dateutil.isLeapYear(2400)' );
|
||||
same( dateutil.isLeapYear(2800), true , 'dateutil.isLeapYear(2800)' );
|
||||
same( dateutil.isLeapYear(1933), false, 'dateutil.isLeapYear(1933)' );
|
||||
same( dateutil.isLeapYear(1999), false, 'dateutil.isLeapYear(1999)' );
|
||||
same( dateutil.isLeapYear(1800), false, 'dateutil.isLeapYear(1800)' );
|
||||
same( dateutil.isLeapYear(1900), false, 'dateutil.isLeapYear(1900)' );
|
||||
same( dateutil.isLeapYear(2100), false, 'dateutil.isLeapYear(2100)' );
|
||||
same( dateutil.isLeapYear(2200), false, 'dateutil.isLeapYear(2200)' );
|
||||
|
||||
});
|
||||
|
||||
test("dateutil.daysInMonth", function () {
|
||||
|
||||
// expect( 10 );
|
||||
ok( typeof dateutil.daysInMonth === 'function', 'dateutil.daysInMonth() is a function' );
|
||||
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 0,1) )), 31, 'dateutil.daysInMonth( Jan. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 1,1) )), 28, 'dateutil.daysInMonth( Feb. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 2,1) )), 31, 'dateutil.daysInMonth( Mar. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 3,1) )), 30, 'dateutil.daysInMonth( Apr. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 4,1) )), 31, 'dateutil.daysInMonth( May. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 5,1) )), 30, 'dateutil.daysInMonth( Jun. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 6,1) )), 31, 'dateutil.daysInMonth( Jul. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 7,1) )), 31, 'dateutil.daysInMonth( Aug. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 8,1) )), 30, 'dateutil.daysInMonth( Sep. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999, 9,1) )), 31, 'dateutil.daysInMonth( Oct. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999,10,1) )), 30, 'dateutil.daysInMonth( Nov. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1999,11,1) )), 31, 'dateutil.daysInMonth( Dec. 1999 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 0,1) )), 31, 'dateutil.daysInMonth( Jan. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 1,1) )), 29, 'dateutil.daysInMonth( Feb. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 2,1) )), 31, 'dateutil.daysInMonth( Mar. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 3,1) )), 30, 'dateutil.daysInMonth( Apr. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 4,1) )), 31, 'dateutil.daysInMonth( May. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 5,1) )), 30, 'dateutil.daysInMonth( Jun. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 6,1) )), 31, 'dateutil.daysInMonth( Jul. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 7,1) )), 31, 'dateutil.daysInMonth( Aug. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 8,1) )), 30, 'dateutil.daysInMonth( Sep. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920, 9,1) )), 31, 'dateutil.daysInMonth( Oct. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920,10,1) )), 30, 'dateutil.daysInMonth( Nov. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(1920,11,1) )), 31, 'dateutil.daysInMonth( Dec. 1920 )' );
|
||||
same( dateutil.daysInMonth(new Date( Date.UTC(2100, 1,1) )), 28, 'dateutil.daysInMonth( Feb. 2100 )' );
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('dateutil.isocalendar', function () {
|
||||
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2005, 0, 1))), [2004,53,6], "2004-W53-6" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2005, 0, 2))), [2004,53,7], "2004-W53-7" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2005,11,31))), [2005,52,6], "2005-W52-6" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2007, 0, 1))), [2007, 1,1], "2007-W01-1" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2007,11,30))), [2007,52,7], "2007-W52-7" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2007,11,31))), [2008, 1,1], "2008-W01-1" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2008, 0, 1))), [2008, 1,2], "2008-W01-2" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2008,11,29))), [2009, 1,1], "2009-W01-1" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2008,11,31))), [2009, 1,3], "2009-W01-3" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2009, 0, 1))), [2009, 1,4], "2009-W01-4" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2009,11,31))), [2009,53,4], "2009-W53-4" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2010, 0, 3))), [2009,53,7], "2009-W53-7" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2009,11,31))), [2009,53,4], "2009-W53-4" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2010, 0, 1))), [2009,53,5], "2009-W53-5" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2010, 0, 2))), [2009,53,6], "2009-W53-6" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2010, 0, 3))), [2009,53,7], "2009-W53-7" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2008,11,28))), [2008,52,7], "2008-W52-7" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2008,11,29))), [2009, 1,1], "2009-W01-1" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2008,11,30))), [2009, 1,2], "2009-W01-2" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2008,11,31))), [2009, 1,3], "2009-W01-3" );
|
||||
same( dateutil.isocalendar(new Date(Date.UTC(2009, 0, 1))), [2009, 1,4], "2009-W01-4" );
|
||||
|
||||
});
|
||||
|
||||
test("dateutil.set", function () {
|
||||
|
||||
// expect( 10 );
|
||||
ok( typeof dateutil.set === 'function', 'dateutil.set() is a function' );
|
||||
|
||||
var dt = new Date(Date.UTC(1975,9,16,10,24,12,50));
|
||||
var bt = dateutil.set(new Date(0), {
|
||||
year: 1975,
|
||||
month: 9,
|
||||
day: 16,
|
||||
hour: 10,
|
||||
minute: 24,
|
||||
second: 12,
|
||||
millisecond: 50
|
||||
});
|
||||
ok( +bt == +dt, 'dateutil.set' );
|
||||
|
||||
// testing rollover mechanism
|
||||
var d = dateutil.set(new Date( Date.UTC( 1999, 1, 20 ) ), {
|
||||
'date': 30,
|
||||
'month': 0
|
||||
});
|
||||
ok( +d === 917654400000, 'prevent rollovers: 1999-02-20 date,month' );
|
||||
|
||||
var d = dateutil.set(new Date( Date.UTC( 1999, 1, 20 ) ), {
|
||||
'month': 0,
|
||||
'date': 30
|
||||
});
|
||||
ok( +d === 917654400000, 'prevent rollovers: 1999-02-20 month,date' );
|
||||
|
||||
var d = dateutil.set(new Date( Date.UTC( 1600, 1, 29 ) ), {
|
||||
'date': 30,
|
||||
'month': 0
|
||||
});
|
||||
same( +d, -11673590400000, 'prevent rollovers: 1600-02-29 date,month' );
|
||||
|
||||
var d = dateutil.set(new Date( Date.UTC( 1600, 1, 29 ) ), {
|
||||
'month': 0,
|
||||
'date': 30
|
||||
});
|
||||
same( +d, -11673590400000, 'prevent rollovers: 1600-02-29 month,date' );
|
||||
|
||||
});
|
||||
|
||||
|
||||
test('dateutil.parse', function(){
|
||||
|
||||
var corduroy_full = new Date(Date.UTC(2011,10,11, 11,11,11, 111));
|
||||
var corduroy_sec = new Date(Date.UTC(2011,10,11, 11,11,11));
|
||||
var corduroy_mfrc = new Date(Date.UTC(2011,10,11, 11,11,6, 660));
|
||||
var corduroy_min = new Date(Date.UTC(2011,10,11, 11,11));
|
||||
var corduroy_hfrc = new Date(Date.UTC(2011,10,11, 11,6,39,600));
|
||||
var corduroy_hour = new Date(Date.UTC(2011,10,11, 11));
|
||||
var corduroy_day = new Date(Date.UTC(2011,10,11));
|
||||
var corduroy_mon = new Date(Date.UTC(2011,10,1));
|
||||
var corduroy_year = new Date(Date.UTC(2011,0,1));
|
||||
var INVALID = new Date('borked')+'';
|
||||
|
||||
// date_and_time
|
||||
same( +dateutil.parse('2011-11-11T11:11:11.111Z'), +corduroy_full, "2011-11-11T11:11:11.111Z" );
|
||||
same( +dateutil.parse('2011-11-11T11:11:11Z'), +corduroy_sec, "2011-11-11T11:11:11Z" );
|
||||
same( +dateutil.parse('2011-11-11T11:11.111Z'), +corduroy_mfrc, "2011-11-11T11:11.111Z" );
|
||||
same( +dateutil.parse('2011-11-11T11:11Z'), +corduroy_min, "2011-11-11T11:11Z" );
|
||||
same( +dateutil.parse('2011-11-11T11.111Z'), +corduroy_hfrc, "2011-11-11T11.111Z" );
|
||||
same( +dateutil.parse('2011-11-11T11Z'), +corduroy_hour, "2011-11-11T11Z" );
|
||||
|
||||
same( +dateutil.parse('2011-11-11T11:11:11.111'), +corduroy_full, "2011-11-11T11:11:11.111" );
|
||||
same( +dateutil.parse('2011-11-11T11:11:11'), +corduroy_sec, "2011-11-11T11:11:11" );
|
||||
same( +dateutil.parse('2011-11-11T11:11.111'), +corduroy_mfrc, "2011-11-11T11:11.111" );
|
||||
same( +dateutil.parse('2011-11-11T11:11'), +corduroy_min, "2011-11-11T11:11" );
|
||||
same( +dateutil.parse('2011-11-11T11.111'), +corduroy_hfrc, "2011-11-11T11.111" );
|
||||
same( +dateutil.parse('2011-11-11T11'), +corduroy_hour, "2011-11-11T11" );
|
||||
|
||||
same( +dateutil.parse('2011-11-11T11:11:11.111+00:00'), +corduroy_full, "2011-11-11T11:11:11.111+00:00" );
|
||||
same( +dateutil.parse('2011-11-11T11:11:11+00:00'), +corduroy_sec, "2011-11-11T11:11:11+00:00" );
|
||||
same( +dateutil.parse('2011-11-11T11:11.111+00:00'), +corduroy_mfrc, "2011-11-11T11:11.111+00:00" );
|
||||
same( +dateutil.parse('2011-11-11T11:11+00:00'), +corduroy_min, "2011-11-11T11:11+00:00" );
|
||||
same( +dateutil.parse('2011-11-11T11.111+00:00'), +corduroy_hfrc, "2011-11-11T11.111+00:00" );
|
||||
same( +dateutil.parse('2011-11-11T11+00:00'), +corduroy_hour, "2011-11-11T11+00:00" );
|
||||
|
||||
same( +dateutil.parse('2011-11-11T11:11:11.111+00'), +corduroy_full, "2011-11-11T11:11:11.111+00" );
|
||||
same( +dateutil.parse('2011-11-11T11:11:11+00'), +corduroy_sec, "2011-11-11T11:11:11+00" );
|
||||
same( +dateutil.parse('2011-11-11T11:11.111+00'), +corduroy_mfrc, "2011-11-11T11:11.111+00" );
|
||||
same( +dateutil.parse('2011-11-11T11:11+00'), +corduroy_min, "2011-11-11T11:11+00" );
|
||||
same( +dateutil.parse('2011-11-11T11.111+00'), +corduroy_hfrc, "2011-11-11T11.111+00" );
|
||||
same( +dateutil.parse('2011-11-11T11+00'), +corduroy_hour, "2011-11-11T11+00" );
|
||||
|
||||
// date
|
||||
same( +dateutil.parse('2011-11-11'), +corduroy_day, "2011-11-11" );
|
||||
same( +dateutil.parse('2011-1111'), +corduroy_day, "2011-1111" );
|
||||
|
||||
// year_and_month
|
||||
same( +dateutil.parse('2011-11'), +corduroy_mon, "2011-11" );
|
||||
same( +dateutil.parse('2011/11'), +corduroy_mon, "2011/11" );
|
||||
same( ''+dateutil.parse('2011-13'), INVALID, "2011-13" ); // should fail!
|
||||
|
||||
// year
|
||||
same( +dateutil.parse('2011'), +corduroy_year, "2011" );
|
||||
same( +dateutil.parse('12011'), +new Date(Date.UTC(12011,0,1)), "12011" );
|
||||
same( +dateutil.parse('-12011'), +new Date(Date.UTC(-12011,0,1)), "-12011" );
|
||||
|
||||
// year_and_week
|
||||
ok( +corduroy_day == +dateutil.parse('2011-W45-5'), "2011-W45-5" );
|
||||
ok( +corduroy_day == +dateutil.parse('2011W45-5'), "2011W45-5" );
|
||||
ok( +corduroy_day == +dateutil.parse('2011W455'), "2011W455" );
|
||||
ok( +new Date(Date.UTC(2011,10,7)) == +dateutil.parse('2011-W45'), "2011-W45" );
|
||||
ok( +new Date(Date.UTC(2011,10,7)) == +dateutil.parse('2011W45'), "2011W45" );
|
||||
|
||||
// year_and_ordinal
|
||||
same( +dateutil.parse('-12011-314'), +new Date( Date.UTC( -12011,0,314 ) ), "-12011-314" );
|
||||
same( +dateutil.parse('2011-314'), +new Date( Date.UTC( 2011,0,314 ) ), "2011-314" );
|
||||
same( ''+dateutil.parse('2011-500'), INVALID, "2011-500" ); // should fail!
|
||||
|
||||
// year_and_quarter
|
||||
var corduroy_q = new Date( Date.UTC( 2011,9,1 ) );
|
||||
ok( +dateutil.parse('-12011-Q4') == +new Date(Date.UTC(-12011,9,1)), "-12011-Q4" );
|
||||
ok( +corduroy_q == +dateutil.parse('2011Q4'), "2011Q4" );
|
||||
ok( +corduroy_q == +dateutil.parse('2011-Q4'), "2011-Q4" );
|
||||
ok( +corduroy_q == +dateutil.parse('2011q4'), "2011q4" );
|
||||
ok( +corduroy_q == +dateutil.parse('2011-q4'), "2011-q4" );
|
||||
|
||||
});
|
||||
|
||||
|
||||
test("dateutil.parse: JSON style calendar dates", function () {
|
||||
for (var i=0,l=test_dates.length; i<l; i++) {
|
||||
var test_date = test_dates[i];
|
||||
var parsed_caldate = dateutil.parse( test_date[0] );
|
||||
var org_timestamp = test_date[2];
|
||||
with (Math) {
|
||||
// I anticipate that there may be some floating point artifacts
|
||||
// in the milliseconds. Currently allowing a variation of a about
|
||||
// a microsecond
|
||||
var diff = parsed_caldate - floor( org_timestamp * 1000 );
|
||||
ok(
|
||||
abs( diff ) <= 1,
|
||||
test_date[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test("dateutil.parse: yyyy-Www-d", function () {
|
||||
for (var i=0,l=test_dates.length; i<l; i++) {
|
||||
var test_date = test_dates[i];
|
||||
with (Math) {
|
||||
var d = new Date( floor( test_date[2] * 1000 ) );
|
||||
d.setUTCHours(0); d.setUTCMinutes(0); d.setUTCSeconds(0); d.setUTCMilliseconds(0);
|
||||
same( +dateutil.parse( test_date[1] ), +d, test_date[1] );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test("dateutil.format", function () {
|
||||
|
||||
// expect( 10 );
|
||||
ok( typeof dateutil.format === 'function', 'dateutil.format() is a function' );
|
||||
|
||||
var d = new Date( Date.UTC( 2001, 2, 10, 17, 16, 18, 13 ) );
|
||||
same( dateutil.format( d, "F j, Y, g:i a"), "March 10, 2001, 5:16 pm", "F j, Y, g:i a" );
|
||||
same( dateutil.format( d, "m.d.y"), "03.10.01", "m.d.y" );
|
||||
same( dateutil.format( d, "j, n, Y"), "10, 3, 2001", "j, n, Y" );
|
||||
same( dateutil.format( d, "Ymd"), "20010310", "Ymd" );
|
||||
same( dateutil.format( d, 'h-i-s, j-m-y, it is w Day'), "05-16-18, 10-03-01, 1631 1618 6 Satpm01", "h-i-s, j-m-y, it is w Day" );
|
||||
same( dateutil.format( d, '\\i\\t \\i\\s \\t\\h\\e jS \\d\\a\\y.'), "it is the 10th day.", "\\i\\t \\i\\s \\t\\h\\e jS \\d\\a\\y." );
|
||||
same( dateutil.format( d, "D M j G:i:s T Y"), "Sat Mar 10 17:16:18 UTC 2001", "D M j G:i:s T Y" );
|
||||
same( dateutil.format( d, 'H:m:s \\m \\i\\s\\ \\m\\o\\n\\t\\h'), "17:03:18 m is month", "H:m:s \\m \\i\\s\\ \\m\\o\\n\\t\\h" );
|
||||
same( dateutil.format( d, "H:i:s"), "17:16:18", "Format: 17:16:18" );
|
||||
|
||||
var d = new Date( Date.UTC( 2006, 7, 10, 15, 5, 8, 123 ) );
|
||||
equals( dateutil.format( d, 'd'), '10', 'format: d' );
|
||||
equals( dateutil.format( d, 'D'), 'Thu', 'format: D' );
|
||||
equals( dateutil.format( d, 'j'), '10', 'format: j' );
|
||||
equals( dateutil.format( d, 'l'), 'Thursday', 'format: l' );
|
||||
equals( dateutil.format( d, 'N'), '4', 'format: N' );
|
||||
equals( dateutil.format( d, 'S'), 'th', 'format: S' );
|
||||
equals( dateutil.format( d, 'w'), '4', 'format: w' );
|
||||
equals( dateutil.format( d, 'z'), '221', 'format: z' );
|
||||
equals( dateutil.format( d, 'W'), '32', 'format: W' );
|
||||
equals( dateutil.format( d, 'F'), 'August', 'format: F' );
|
||||
equals( dateutil.format( d, 'm'), '08', 'format: m' );
|
||||
equals( dateutil.format( d, 'M'), 'Aug', 'format: M' );
|
||||
equals( dateutil.format( d, 'n'), '8', 'format: n' );
|
||||
equals( dateutil.format( d, 't'), '31', 'format: t' );
|
||||
equals( dateutil.format( d, 'L'), '0', 'format: L' );
|
||||
equals( dateutil.format( d, 'o'), '2006', 'format: o' );
|
||||
equals( dateutil.format( d, 'Y'), '2006', 'format: Y' );
|
||||
equals( dateutil.format( d, 'y'), '06', 'format: y' );
|
||||
equals( dateutil.format( d, 'a'), 'pm', 'format: a' );
|
||||
equals( dateutil.format( d, 'A'), 'PM', 'format: A' );
|
||||
equals( dateutil.format( d, 'B'), 'B', 'format: B' ); // not supported
|
||||
equals( dateutil.format( d, 'g'), '3', 'format: g' );
|
||||
equals( dateutil.format( d, 'G'), '15', 'format: G' );
|
||||
equals( dateutil.format( d, 'h'), '03', 'format: h' );
|
||||
equals( dateutil.format( d, 'H'), '15', 'format: H' );
|
||||
equals( dateutil.format( d, 'i'), '05', 'format: i' );
|
||||
equals( dateutil.format( d, 's'), '08', 'format: s' );
|
||||
equals( dateutil.format( d, 'u'), '000123', 'format: u' );
|
||||
equals( dateutil.format( d, 'e'), 'UTC', 'format: e' );
|
||||
equals( dateutil.format( d, 'I'), 'I', 'format: I' ); // not supported
|
||||
equals( dateutil.format( d, 'O'), '+0000', 'format: O' );
|
||||
equals( dateutil.format( d, 'P'), '+00:00', 'format: P' );
|
||||
equals( dateutil.format( d, 'T'), 'UTC', 'format: T' );
|
||||
equals( dateutil.format( d, 'Z'), 'Z', 'format: Z' ); // not supported
|
||||
equals( dateutil.format( d, 'c'), '2006-08-10T15:05:08.123Z', 'format: c' );
|
||||
equals( dateutil.format( d, 'r'), 'Thu, 10 Aug 2006 15:05:08 +0000', 'format: r' );
|
||||
equals( dateutil.format( d, 'U'), '1155222308', 'format: U' );
|
||||
|
||||
var d = new Date( Date.UTC( 1961, 0, 3, 1, 51, 10, 1 ) );
|
||||
equals( dateutil.format( d, 'd'), '03', 'format: d' );
|
||||
equals( dateutil.format( d, 'D'), 'Tue', 'format: D' );
|
||||
equals( dateutil.format( d, 'j'), '3', 'format: j' );
|
||||
equals( dateutil.format( d, 'l'), 'Tuesday', 'format: l' );
|
||||
equals( dateutil.format( d, 'N'), '2', 'format: N' );
|
||||
equals( dateutil.format( d, 'S'), 'rd', 'format: S' );
|
||||
equals( dateutil.format( d, 'w'), '2', 'format: w' );
|
||||
equals( dateutil.format( d, 'z'), '2', 'format: z' );
|
||||
equals( dateutil.format( d, 'W'), '01', 'format: W' );
|
||||
equals( dateutil.format( d, 'F'), 'January', 'format: F' );
|
||||
equals( dateutil.format( d, 'm'), '01', 'format: m' );
|
||||
equals( dateutil.format( d, 'M'), 'Jan', 'format: M' );
|
||||
equals( dateutil.format( d, 'n'), '1', 'format: n' );
|
||||
equals( dateutil.format( d, 't'), '31', 'format: t' );
|
||||
equals( dateutil.format( d, 'L'), '0', 'format: L' );
|
||||
equals( dateutil.format( d, 'o'), '1961', 'format: o' );
|
||||
equals( dateutil.format( d, 'Y'), '1961', 'format: Y' );
|
||||
equals( dateutil.format( d, 'y'), '61', 'format: y' );
|
||||
equals( dateutil.format( d, 'a'), 'am', 'format: a' );
|
||||
equals( dateutil.format( d, 'A'), 'AM', 'format: A' );
|
||||
equals( dateutil.format( d, 'B'), 'B', 'format: B' ); // unsupported
|
||||
equals( dateutil.format( d, 'g'), '1', 'format: g' );
|
||||
equals( dateutil.format( d, 'G'), '1', 'format: G' );
|
||||
equals( dateutil.format( d, 'h'), '01', 'format: h' );
|
||||
equals( dateutil.format( d, 'H'), '01', 'format: H' );
|
||||
equals( dateutil.format( d, 'i'), '51', 'format: i' );
|
||||
equals( dateutil.format( d, 's'), '10', 'format: s' );
|
||||
equals( dateutil.format( d, 'u'), '000001', 'format: u' );
|
||||
equals( dateutil.format( d, 'e'), 'UTC', 'format: e' );
|
||||
equals( dateutil.format( d, 'I'), 'I', 'format: I' ); // not supported
|
||||
equals( dateutil.format( d, 'O'), '+0000', 'format: O' );
|
||||
equals( dateutil.format( d, 'P'), '+00:00', 'format: P' );
|
||||
equals( dateutil.format( d, 'T'), 'UTC', 'format: T' );
|
||||
equals( dateutil.format( d, 'Z'), 'Z', 'format: Z' ); // not supported
|
||||
equals( dateutil.format( d, 'c'), '1961-01-03T01:51:10.001Z', 'format: c' );
|
||||
equals( dateutil.format( d, 'r'), 'Tue, 03 Jan 1961 01:51:10 +0000', 'format: r' );
|
||||
equals( dateutil.format( d, 'U'), '-283817329', 'format: U' );
|
||||
|
||||
// allow transfering to prototype
|
||||
Date.prototype.format = dateutil.format;
|
||||
var d = new Date( Date.UTC(1975, 9, 16) );
|
||||
equals( d.format('Y-m-d'), "1975-10-16", 'format prototype assignment' );
|
||||
try { delete Date.prototype.format; }
|
||||
catch (err) {}
|
||||
|
||||
});
|
||||
|
||||
|
||||
test("dateutil.today", function () {
|
||||
|
||||
// expect( 10 );
|
||||
ok( typeof dateutil.today === 'function', 'dateutil.today() is a function' );
|
||||
|
||||
var d = new Date();
|
||||
var today = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
||||
|
||||
same( +dateutil.today(), +today, 'dateutil.today()' );
|
||||
same( +dateutil.today(), +dateutil.set(new Date, { hour:0, minute:0, second:0, millisecond:0 }), 'dateutil.today() vs. dateutil.set()' );
|
||||
|
||||
});
|
||||
|
||||
|
||||
test("dateutil.now", function () {
|
||||
var d = new Date(), now = dateutil.now();
|
||||
|
||||
// expect( 10 );
|
||||
ok( typeof dateutil.now === 'function', 'dateutil.now() is a function' );
|
||||
same( +d, +now, 'dateutil.now()' );
|
||||
|
||||
});
|
||||
|
||||
|
||||
test("dateutil._", function () {
|
||||
|
||||
ok( typeof dateutil._ === 'function', 'dateutil._() is a function' );
|
||||
|
||||
// provide translations for icelandic:
|
||||
dateutil.lang.is = {
|
||||
"January":"janúar", "February":"febrúar", "March":"mars", "April":"apríl", "May":"maí", "June":"júní", "July":"júlí", "August":"ágúst", "September":"september", "October":"október", "November":"nóvember", "December":"desember",
|
||||
"Sunday":"sunnudagur", "Monday":"mánudagur", "Tuesday":"þriðjudagur", "Wednesday":"miðvikudagur", "Thursday":"fimmtudagur", "Friday":"föstudagur", "Saturday":"laugardagur",
|
||||
"Jan":"jan", "Feb":"feb", "Mar":"mar", "Apr":"apr", "Jun":"jún", "Jul":"júl", "Aug":"ágú", "Sep":"sep", "Oct":"okt", "Nov":"nóv", "Dec":"des",
|
||||
"Sun":"sun", "Mon":"mán", "Tue":"þri", "Wed":"mið", "Thu":"fim", "Fri":"fös", "Sat":"lau"
|
||||
};
|
||||
var d = new Date( Date.UTC( 2006, 7, 10, 15, 5, 8, 123 ) );
|
||||
equals( dateutil.format( d, 'D', 'is' ), 'fim', 'format: D' );
|
||||
equals( dateutil.format( d, 'l', 'is' ), 'fimmtudagur', 'format: l' );
|
||||
equals( dateutil.format( d, 'F', 'is' ), 'ágúst', 'format: F' );
|
||||
equals( dateutil.format( d, 'M', 'is' ), 'ágú', 'format: M' );
|
||||
|
||||
dateutil._ = function () { return 'BORK'; };
|
||||
equals( dateutil.format( d, 'D', 'is' ), 'BORK', 'format: D' );
|
||||
equals( dateutil.format( d, 'l', 'is' ), 'BORK', 'format: l' );
|
||||
equals( dateutil.format( d, 'F', 'is' ), 'BORK', 'format: F' );
|
||||
equals( dateutil.format( d, 'M', 'is' ), 'BORK', 'format: M' );
|
||||
|
||||
});
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* This file implements a subset of the QUnit functions/features to allow
|
||||
* running simple QUnit tests in Node.JS.
|
||||
*
|
||||
* Copyright (c) 2009 Borgar Þorsteinsson
|
||||
* Licensed under the terms of the MIT (LICENSE.txt) software license.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// the files to load for the test, test itself last:
|
||||
|
||||
var files = [
|
||||
'dateutil.js',
|
||||
'dateutil.testdates.js',
|
||||
'dateutil.test.js'
|
||||
];
|
||||
|
||||
|
||||
/* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- */
|
||||
|
||||
var sys = require('sys'),
|
||||
fs = require('fs'),
|
||||
Script = process.binding('evals').Script,
|
||||
assert = require('assert')
|
||||
;
|
||||
var tests = [];
|
||||
var current_test;
|
||||
var sandbox = {
|
||||
|
||||
expect: function ( num ) {
|
||||
current_test.expected = num;
|
||||
},
|
||||
|
||||
ok: function ( value, message ) {
|
||||
current_test.results.push([ !!value, message ]);
|
||||
},
|
||||
|
||||
equals: function ( rest, exp, msg ) {
|
||||
try {
|
||||
assert.equal(rest, exp);
|
||||
}
|
||||
catch ( err ) {
|
||||
current_test.results.push([ false, msg + ', expected: '+exp+' result: ' + rest ]);
|
||||
return;
|
||||
}
|
||||
current_test.results.push([ true, msg ]);
|
||||
},
|
||||
|
||||
same: function ( rest, exp, msg ) {
|
||||
try {
|
||||
assert.deepEqual(rest, exp);
|
||||
}
|
||||
catch ( err ) {
|
||||
current_test.results.push([ false, msg + ', expected: '+exp+' result: ' + rest ]);
|
||||
return;
|
||||
}
|
||||
current_test.results.push([ true, msg ]);
|
||||
},
|
||||
|
||||
test: function ( testName /*, expected, callback, async */ ) {
|
||||
|
||||
var tmap = {
|
||||
'object':'environment',
|
||||
'boolean':'async',
|
||||
'function':'callback',
|
||||
'number':'expected'
|
||||
};
|
||||
|
||||
current_test = {
|
||||
name: testName,
|
||||
expected: null,
|
||||
callback: null,
|
||||
context: {},
|
||||
async: false, // TODO: async doesn't actually do anything :-/
|
||||
error: false,
|
||||
results: []
|
||||
};
|
||||
tests.push( current_test );
|
||||
|
||||
for (var i=1,l=arguments.length; i<l; i++) {
|
||||
current_test[ tmap[ typeof arguments[i] ] || 'error' ] = arguments[i];
|
||||
}
|
||||
try {
|
||||
current_test.callback.call( current_test.context );
|
||||
}
|
||||
catch ( err ) {
|
||||
current_test.results.push([ false, err ]);
|
||||
}
|
||||
|
||||
if ( current_test.expected ) {
|
||||
if ( current_test.expected != current_test.results.length ) {
|
||||
current_test.results.push([ false, 'Expected '+current_test.expected+' assertions, but '+current_test.results.length+' were run' ]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function pad ( s, l ) {
|
||||
s = l + s;
|
||||
return s.substr( s.length - l.length, l.length );
|
||||
}
|
||||
|
||||
|
||||
// ------------
|
||||
// -- runner --
|
||||
// ------------
|
||||
|
||||
var test_script = files.map(function ( fn ) {
|
||||
try {
|
||||
return fs.readFileSync( fn );
|
||||
}
|
||||
catch ( err ) {
|
||||
console.log('Critical failure: Cannot load ' + fn );
|
||||
process.exit(1);
|
||||
}
|
||||
}).join('\n;\n');
|
||||
|
||||
|
||||
console.log('');
|
||||
|
||||
// time it...
|
||||
console.time('Tests total time');
|
||||
|
||||
// run the tests
|
||||
Script.runInNewContext( test_script, sandbox );
|
||||
|
||||
// reports
|
||||
var total_ok = 0, total_fail = 0, total_tests = 0;
|
||||
for (var t=0,tl=tests.length; t<tl; t++) {
|
||||
|
||||
var test = tests[t];
|
||||
var report = [];
|
||||
var ok = 0, fail = 0;
|
||||
|
||||
for (var r=0,rl=test.results.length; r<rl; r++) {
|
||||
var res = test.results[r];
|
||||
if ( res[0] ) {
|
||||
ok++;
|
||||
total_ok++;
|
||||
}
|
||||
else {
|
||||
fail++;
|
||||
total_fail++;
|
||||
}
|
||||
report.push( ' ' + pad(r,' ') + '. ' + (res[0]?'':'[FAIL]: ') + res[1] );
|
||||
}
|
||||
|
||||
total_tests += test.results.length;
|
||||
|
||||
console.log( '' + pad(t,' ') + '. ' + test.name + ' ('+fail+', '+ok+', '+(test.results.length)+')' );
|
||||
|
||||
if ( fail ) {
|
||||
console.log( report.join('\n') );
|
||||
console.log('');
|
||||
}
|
||||
|
||||
}
|
||||
console.log('');
|
||||
console.timeEnd('Tests total time');
|
||||
console.log(total_ok+' tests of '+total_tests+' passed, '+total_fail+' failed');
|
||||
|
||||
+4002
File diff suppressed because it is too large
Load Diff
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "dateutil",
|
||||
"version": "0.1.0",
|
||||
"author": "Borgar Þorsteinsson <borgar@borgar.net> (http://borgar.net/)",
|
||||
"description": "A date parsing and formatting library. Complete ISO 8061 date/time parsing support including quarters and years. Full PHP style date formatting.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/borgar/dateutil.git"
|
||||
},
|
||||
"main": "./dateutil.js",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"tags": [ "date", "time", "format", "PHP", "parse", "ISO 8601" ],
|
||||
"bugs": {
|
||||
"url": "https://github.com/borgar/dateutil/issues"
|
||||
}
|
||||
}
|
||||
Generated
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "webapp",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"dateutil": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dateutil": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/dateutil/-/dateutil-0.1.0.tgz",
|
||||
"integrity": "sha512-bI8Lm50mageQER89AvYr+bAxXn4MCAkjSg3M58QY2evrIypUk2aCQcMZ86f31tjmt2ceMrPkrHkhItYqOJsObw==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"dateutil": "^0.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
@@ -0,0 +1,5 @@
|
||||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2023
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "vite-project",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.11.0",
|
||||
"konva": "^9.3.22",
|
||||
"querystring": "^0.2.1",
|
||||
"vue": "^3.5.17",
|
||||
"vue-konva": "^3.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.0",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.4",
|
||||
"vue-tsc": "^2.2.12"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import index from './components/check/index.vue'
|
||||
import manage from './pages/manage.vue'
|
||||
const selectedKeys1 = ref (['1']);
|
||||
let activeItem = ref(1);
|
||||
|
||||
const handleSelectd= (e: any) => {
|
||||
console.log('click ', e);
|
||||
activeItem.value = e.key;
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<a-layout class="container">
|
||||
<a-layout-header class="header">
|
||||
<div class="logo" />
|
||||
<a-menu
|
||||
v-model:selectedKeys="selectedKeys1"
|
||||
theme="dark"
|
||||
mode="horizontal"
|
||||
:style="{ lineHeight: '64px' }"
|
||||
@select="handleSelectd"
|
||||
>
|
||||
<a-menu-item key="1">发票查验</a-menu-item>
|
||||
<a-menu-item key="2">发票管理</a-menu-item>
|
||||
<a-menu-item key="3">发票复验</a-menu-item>
|
||||
<a-menu-item key="4">发票统计</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-header>
|
||||
<a-layout-content style="padding: 0 10px">
|
||||
<a-layout style="padding: 24px 0; background: #fff">
|
||||
<index v-if="activeItem == 1" />
|
||||
<manage v-if="activeItem == 2" />
|
||||
</a-layout>
|
||||
</a-layout-content>
|
||||
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
#components-layout-demo-top-side .logo {
|
||||
float: left;
|
||||
height: 31px;
|
||||
margin: 16px 24px 16px 0;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.ant-row-rtl #components-layout-demo-top-side .logo {
|
||||
float: right;
|
||||
margin: 16px 0 16px 24px;
|
||||
}
|
||||
.container {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<a-layout-content>
|
||||
<a-row >
|
||||
<a-col :span="15">
|
||||
<!-- <v-stage v-if="fileType === 'img'" :config="stageConfig">-->
|
||||
<!-- <v-layer>-->
|
||||
<!--<!– <v-image :config="{image: image,x:0,y:0}" />–>-->
|
||||
<!-- <v-shape :config="{x: 100, y: 100, width: 100, height: 100, fill: 'red'}" />-->
|
||||
|
||||
<!-- </v-layer>-->
|
||||
|
||||
<!-- </v-stage>-->
|
||||
<img v-if="fileType === 'img'" :src="fileUrl" alt="" class="pdf" />
|
||||
<iframe v-if="fileType==='pdf'" :src="fileUrl" class="pdf">
|
||||
</iframe>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form
|
||||
:model="formState"
|
||||
name="basic"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
autocomplete="off"
|
||||
@finish="onFinish"
|
||||
@finishFailed="onFinishFailed"
|
||||
>
|
||||
|
||||
<a-form-item label="发票类型">
|
||||
<a-input v-model:value="formState.invoiceType" />
|
||||
</a-form-item>
|
||||
<a-form-item label="发票号码">
|
||||
<a-input v-model:value="formState.invoiceNumber" />
|
||||
</a-form-item>
|
||||
<a-form-item label="开票日期">
|
||||
<a-input v-model:value="formState.invoiceDate" />
|
||||
</a-form-item>
|
||||
<a-form-item label="不含税金额">
|
||||
<a-input v-model:value="formState.invoiceAmountPreTax" />
|
||||
</a-form-item>
|
||||
<a-form-item label="税额">
|
||||
<a-input v-model:value="formState.invoiceTax" />
|
||||
</a-form-item>
|
||||
<a-form-item label="价税合计">
|
||||
<a-input v-model:value="formState.totalAmount" />
|
||||
</a-form-item>
|
||||
<a-form-item label="销方名称">
|
||||
<a-input v-model:value="formState.sellerName" />
|
||||
</a-form-item>
|
||||
<a-form-item label="销方税号">
|
||||
<a-input v-model:value="formState.sellerTaxNumber" />
|
||||
</a-form-item>
|
||||
<a-form-item label="购方名称">
|
||||
<a-input v-model:value="formState.purchaserName" />
|
||||
</a-form-item>
|
||||
<a-form-item label="购方税号">
|
||||
<a-input v-model:value="formState.purchaserTaxNumber" />
|
||||
</a-form-item>
|
||||
<a-form-item label="发票代码">
|
||||
<a-input v-model:value="formState.invoiceCode" />
|
||||
</a-form-item>
|
||||
<!-- <a-form-item >-->
|
||||
<!-- <a-row>-->
|
||||
<!-- <a-col :span="12" offset="24">-->
|
||||
<!-- <a-space>-->
|
||||
<!-- <a-button type="primary" html-type="submit">识别发票</a-button>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </a-col>-->
|
||||
<!-- </a-row>-->
|
||||
<!-- </a-form-item>-->
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ref,onMounted } from 'vue';
|
||||
import request from "../../utils/request.ts";
|
||||
import {baseURL} from "../../utils/baseurl.ts";
|
||||
|
||||
export type FormState = {
|
||||
"invoiceCode": string,//发票代码
|
||||
"invoiceNumber": string,//发票号码
|
||||
"invoiceDate": string,//开票日期
|
||||
"purchaserName": string,//购方名称
|
||||
"purchaserTaxNumber": string,//购方税号
|
||||
"invoiceAmountPreTax": number,//不含税金额
|
||||
"invoiceTax": number,//税额
|
||||
"totalAmount": number,//价税合计
|
||||
"sellerName": string,//销方名称
|
||||
"sellerTaxNumber": string,//销方税号
|
||||
"sellerContactInfo": string,//销方联系方式
|
||||
"sellerBankAccountInfo": string,
|
||||
"drawer": string,//开票人
|
||||
"remarks": string,//备注
|
||||
"title": string,//标题
|
||||
"invoiceType": string,//发票类型
|
||||
"specialTag": string,//特殊标记
|
||||
"invoiceDetails": [{
|
||||
"itemName": string,//项目名称
|
||||
"amount": number,//金额
|
||||
"taxRate": number,//税率
|
||||
"tax": number//税额
|
||||
}]
|
||||
}
|
||||
const props = defineProps({ filePath: String,formState: String,});
|
||||
const emit= defineEmits(['update:formState'])
|
||||
|
||||
const fileType = ref<string>('img')
|
||||
onMounted(() => {
|
||||
// alert(props.filePath)
|
||||
if(props.filePath){
|
||||
fileType.value = props.filePath.split('.').pop() ==='pdf'?'pdf':'img'
|
||||
request.get('/recognize?filePath='+encodeURIComponent(props.filePath)).then((res) => {
|
||||
console.log(res.data)
|
||||
formState.value = res.data
|
||||
emit('update:formState', JSON.stringify(res.data))
|
||||
})
|
||||
fileUrl.value = baseURL+"/"+props.filePath
|
||||
}
|
||||
})
|
||||
|
||||
//
|
||||
const fileUrl = ref('')
|
||||
|
||||
|
||||
|
||||
let formState = ref<FormState>({
|
||||
drawer: "",
|
||||
invoiceAmountPreTax: 0,
|
||||
invoiceCode: "",
|
||||
invoiceDate: "",
|
||||
invoiceDetails: [{amount: 0, itemName: "", tax: 0, taxRate: 0}],
|
||||
invoiceNumber: "",
|
||||
invoiceTax: 0,
|
||||
invoiceType: "",
|
||||
purchaserName: "",
|
||||
purchaserTaxNumber: "",
|
||||
remarks: "",
|
||||
sellerBankAccountInfo: "",
|
||||
sellerContactInfo: "",
|
||||
sellerName: "",
|
||||
sellerTaxNumber: "",
|
||||
specialTag: "",
|
||||
title: "",
|
||||
totalAmount: 0
|
||||
});
|
||||
const onFinish = (values: any) => {
|
||||
console.log('Success:', values);
|
||||
};
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.pdf{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import {h, ref} from 'vue';
|
||||
import {
|
||||
UploadOutlined,
|
||||
FileDoneOutlined,
|
||||
FileProtectOutlined,
|
||||
// SmileOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import type {StepProps} from "ant-design-vue"
|
||||
import Check from './Check.vue'
|
||||
import upload from './upload.vue'
|
||||
import Report from "./report.vue"
|
||||
// 步骤
|
||||
const items = [
|
||||
{
|
||||
title: '上传发票',
|
||||
icon: h(UploadOutlined),
|
||||
},
|
||||
{
|
||||
title: '识别发票',
|
||||
icon: h(FileDoneOutlined),
|
||||
},
|
||||
{
|
||||
title: '验证发票',
|
||||
icon: h(FileProtectOutlined),
|
||||
},
|
||||
] as StepProps[];
|
||||
// 文件路径
|
||||
const filePath = ref('');
|
||||
const current = ref<number>(0);
|
||||
// 下一步
|
||||
const nextPage = () => {
|
||||
current.value ++;
|
||||
};
|
||||
// 上一步
|
||||
const previousPage = () => {
|
||||
current.value--;
|
||||
};
|
||||
// 保存
|
||||
const saveReport = () => {
|
||||
console.log('保存成功')
|
||||
}
|
||||
// 表单数据
|
||||
const formState = ref<string>('');
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-steps :current="current" :items="items"></a-steps>
|
||||
<a-layout>
|
||||
<a-layout-content class="container">
|
||||
<upload v-if="current == 0" v-model:filePath="filePath" />
|
||||
<Check v-if="current == 1" v-model:filePath="filePath" v-model:formState="formState"/>
|
||||
<report v-if="current == 2" v-model:filePath="filePath" v-model:formState="formState" />
|
||||
</a-layout-content>
|
||||
<a-layout-footer>
|
||||
<a-row>
|
||||
<a-col :span="12" offset="16">
|
||||
<a-space>
|
||||
<a-button type="default" v-if="current > 0" @click="previousPage">上一步</a-button>
|
||||
<a-button type="primary" v-if="current < items.length-1 " @click="nextPage">下一步</a-button>
|
||||
<a-button type="dashed" v-if="current == items.length-1" @click="saveReport">完成</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-layout-footer>
|
||||
</a-layout>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ant-steps{
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<a-layout-content>
|
||||
<a-result
|
||||
:status="formState.status"
|
||||
:title="formState.cyjgxx"
|
||||
>
|
||||
<template #subTitle >
|
||||
<p v-if="formState.status === 'success'">查验次数: {{formState.inspectionAmount}}</p>
|
||||
<p v-if="formState.status === 'success'">已存入发票库</p>
|
||||
</template>
|
||||
</a-result>
|
||||
</a-layout-content>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import {ref,onMounted } from 'vue';
|
||||
import request from "../../utils/request.ts";
|
||||
const props = defineProps({ filePath: String });
|
||||
import {baseURL} from "../../utils/baseurl.ts";
|
||||
|
||||
const fileType = ref<string>('img')
|
||||
onMounted(() => {
|
||||
// alert(props.filePath)
|
||||
if(props.filePath){
|
||||
fileType.value = props.filePath.split('.').pop() ==='pdf'?'pdf':'img'
|
||||
request.get('/verify?filePath='+encodeURIComponent(props.filePath)).then((res) => {
|
||||
console.log(res)
|
||||
formState.value = res.data
|
||||
})
|
||||
fileUrl.value = baseURL+"/"+props.filePath
|
||||
}
|
||||
})
|
||||
|
||||
//
|
||||
const fileUrl = ref('')
|
||||
|
||||
let formState = ref({
|
||||
status:"",
|
||||
inspectionAmount: "",
|
||||
cyjgxx: "",
|
||||
verify_time: "",
|
||||
});
|
||||
|
||||
</script>
|
||||
<style scoped>
|
||||
.pdf{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import type { UploadProps} from "ant-design-vue";
|
||||
import {ref} from "vue";
|
||||
import {
|
||||
UploadOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import {baseURL} from "../../utils/baseurl.ts";
|
||||
const fileList1 = ref<UploadProps['fileList']>([]);
|
||||
const props = defineProps({ filePath: String });
|
||||
const emit= defineEmits(['update:filePath'])
|
||||
const handleRemove = (file: UploadProps['fileList']) => {
|
||||
console.log( file)
|
||||
return false;
|
||||
};
|
||||
console.log(props.filePath)
|
||||
const handleUpload = (options: any) => {
|
||||
const { file, onSuccess, onError, onProgress } = options;
|
||||
|
||||
// 创建 FormData 对象
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
// 使用 XMLHttpRequest 进行上传
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
// 监听上传进度
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
onProgress({ percent: (event.loaded / event.total) * 100 }, file);
|
||||
}
|
||||
};
|
||||
|
||||
// 上传成功回调
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
console.log(xhr.responseText);
|
||||
onSuccess(xhr.responseText, file);
|
||||
let res = JSON.parse(xhr.responseText)
|
||||
emit('update:filePath', res.file_path)
|
||||
} else {
|
||||
onError(new Error(`Upload failed with status ${xhr.status}`));
|
||||
}
|
||||
};
|
||||
// 上传错误回调
|
||||
xhr.onerror = () => {
|
||||
onError(new Error('Network error'));
|
||||
};
|
||||
|
||||
// 发送请求到服务器
|
||||
xhr.open('POST', baseURL+'/upload');
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-layout-content class="container">
|
||||
<a-upload
|
||||
v-model:file-list="fileList1"
|
||||
:customRequest="handleUpload"
|
||||
list-type="picture"
|
||||
class="upload-list-inline"
|
||||
:max-count="1"
|
||||
@remove="handleRemove"
|
||||
>
|
||||
<a-button >
|
||||
<upload-outlined></upload-outlined>
|
||||
上传发票
|
||||
</a-button>
|
||||
</a-upload>
|
||||
|
||||
</a-layout-content>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
margin-top: 50px;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
import { createApp } from 'vue'
|
||||
import Antd from 'ant-design-vue';
|
||||
import './style.css'
|
||||
import App from './App.vue'
|
||||
import 'ant-design-vue/dist/reset.css';
|
||||
import VueKonva from 'vue-konva';
|
||||
const app = createApp(App)
|
||||
app.use(Antd);
|
||||
app.use(VueKonva);
|
||||
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,356 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {h, onMounted, ref} from "vue";
|
||||
import {
|
||||
SearchOutlined,
|
||||
FileSearchOutlined,
|
||||
PlusOutlined
|
||||
} from '@ant-design/icons-vue'
|
||||
import request from '../utils/request.ts';
|
||||
import {baseURL} from "../utils/baseurl.ts";
|
||||
import type {TableProps, UploadChangeParam, UploadProps} from "ant-design-vue";
|
||||
import { message } from 'ant-design-vue';
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import type {UploadFile} from "ant-design-vue/es/upload/interface";
|
||||
import type {Key} from "ant-design-vue/es/_util/type";
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
interface DataType {
|
||||
id: string;
|
||||
status: string;
|
||||
desc: string;
|
||||
desc_list: string;
|
||||
}
|
||||
const columns = [
|
||||
// {
|
||||
// title: '全选',
|
||||
// dataIndex: 'status',
|
||||
// key: 'status',
|
||||
// showSelection: true,
|
||||
// },
|
||||
{
|
||||
title: '发票号码',
|
||||
dataIndex: 'invoiceNumber',
|
||||
key: 'invoiceNumber',
|
||||
// showSelection: true,
|
||||
},
|
||||
{
|
||||
title: '销方名称',
|
||||
dataIndex: 'sellerName',
|
||||
key: 'sellerName',
|
||||
},
|
||||
{
|
||||
title: '销方税号',
|
||||
dataIndex: 'sellerTaxNumber',
|
||||
key: 'sellerTaxNumber',
|
||||
},
|
||||
{
|
||||
title: '不含税金额',
|
||||
dataIndex: 'invoiceAmountPreTax',
|
||||
key: 'invoiceAmountPreTax',
|
||||
},
|
||||
{
|
||||
title: '税额',
|
||||
dataIndex: 'invoiceTax',
|
||||
key: 'invoiceTax',
|
||||
},
|
||||
{
|
||||
title: '价税合计',
|
||||
dataIndex: 'totalAmount',
|
||||
key: 'totalAmount',
|
||||
},
|
||||
{
|
||||
title: '开票日期',
|
||||
dataIndex: 'invoiceDate',
|
||||
key: 'invoiceDate',
|
||||
},
|
||||
{
|
||||
title: '查验结果',
|
||||
dataIndex: 'verify_status',
|
||||
key: 'verify_status',
|
||||
},
|
||||
{
|
||||
title: '查验次数',
|
||||
dataIndex: 'inspectionAmount',
|
||||
key: 'inspectionAmount',
|
||||
},
|
||||
{
|
||||
title: '查验时间',
|
||||
dataIndex: 'verify_time',
|
||||
key: 'verify_time',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
}
|
||||
|
||||
]
|
||||
// 数据
|
||||
let data = ref( [])
|
||||
|
||||
let searchParams = ref({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
total: 0,
|
||||
params: {
|
||||
verify_status: 'success',
|
||||
value: null,
|
||||
verify_time: null,
|
||||
}
|
||||
});
|
||||
// 下拉框
|
||||
const rowSelection: TableProps['rowSelection'] = {
|
||||
onChange: (selectedRowKeys: Key[], selectedRows: DataType[]) => {
|
||||
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
|
||||
},
|
||||
getCheckboxProps: (record: DataType) => ({
|
||||
disabled: record.status === 'yes', // Column configuration not to be checked
|
||||
name: record.status,
|
||||
}),
|
||||
};
|
||||
// 弹窗
|
||||
const openModal = ref(false);
|
||||
const modalTitle = ref('')
|
||||
const confirmLoading = ref(false);
|
||||
const formData = ref({
|
||||
id: '',
|
||||
status: false,
|
||||
desc: '',
|
||||
desc_files: '',
|
||||
fileIds: new Array<String>(),
|
||||
});
|
||||
|
||||
const handleOk = () => {
|
||||
confirmLoading.value = true;
|
||||
request.post("/updateInvoice", {
|
||||
invoiceId: formData.value.id,
|
||||
status: formData.value.status?'yes':'no',
|
||||
desc: formData.value.desc,
|
||||
desc_files: formData.value.fileIds.join(','),
|
||||
}).then(( res ) => {
|
||||
if(res.status == 200){
|
||||
openModal.value = false;
|
||||
confirmLoading.value = false;
|
||||
messageApi.success("修改成功")
|
||||
}
|
||||
})
|
||||
};
|
||||
const handleOpen = (record:any) => {
|
||||
if (record.status === 'yes') {
|
||||
modalTitle.value = "备注"
|
||||
}else {
|
||||
modalTitle.value = "入账"
|
||||
}
|
||||
formData.value = Object.assign({}, record);
|
||||
formData.value.status = (record.status === 'yes')
|
||||
formData.value.fileIds = record.desc_files.split(',');
|
||||
if (record.desc_files) {
|
||||
let paths = record.desc_files.split(",")
|
||||
fileList.value = []
|
||||
for(let i = 0; i < paths.length; i++) {
|
||||
fileList.value.push({
|
||||
uid: paths[i],
|
||||
name: paths[i].split("/")[2],
|
||||
url: baseURL+"/"+paths[i],
|
||||
});
|
||||
}
|
||||
}
|
||||
openModal.value = true;
|
||||
};
|
||||
// 数据列表
|
||||
const getList = () => {
|
||||
request.post("/listInvoice", {
|
||||
verify_status : searchParams.value.params.verify_status,
|
||||
value : searchParams.value.params.value,
|
||||
verify_time : searchParams.value.params.verify_time,
|
||||
}).then(( res ) => {
|
||||
console.log(res)
|
||||
data.value = res.data
|
||||
})
|
||||
}
|
||||
const fileList = ref<UploadProps['fileList']>([])
|
||||
const previewVisible = ref(false);
|
||||
const previewImage = ref('');
|
||||
const previewTitle = ref('');
|
||||
|
||||
function getBase64(file: File) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result);
|
||||
reader.onerror = error => reject(error);
|
||||
});
|
||||
}
|
||||
//
|
||||
const handleCancelPreview = () => {
|
||||
previewVisible.value = false;
|
||||
previewTitle.value = '';
|
||||
};
|
||||
const handlePreview = async (file: any) => {
|
||||
if (!file.url && !file.preview) {
|
||||
file.preview = (await getBase64(file.originFileObj)) as string;
|
||||
}
|
||||
previewImage.value = file.url || file.preview;
|
||||
previewVisible.value = true;
|
||||
previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1);
|
||||
};
|
||||
|
||||
const handleFileChange = (info: UploadChangeParam) => {
|
||||
if(info.file.status === 'done'){
|
||||
info.file.uid = info.file.response.file_path
|
||||
formData.value.fileIds.push(info.file.response.file_path)
|
||||
}
|
||||
}
|
||||
const handleFileRemove = (file: UploadFile ) => {
|
||||
let index = formData.value.fileIds.indexOf(file.uid)
|
||||
formData.value.fileIds.splice(index, 1);
|
||||
}
|
||||
const handleUpload = (options: any) => {
|
||||
const { file, onSuccess, onError, onProgress } = options;
|
||||
// 创建 FormData 对象
|
||||
const formData1 = new FormData();
|
||||
formData1.append('file', file);
|
||||
// 使用 XMLHttpRequest 进行上传
|
||||
const xhr = new XMLHttpRequest();
|
||||
// 监听上传进度
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) {
|
||||
onProgress({ percent: (event.loaded / event.total) * 100 }, file);
|
||||
}
|
||||
};
|
||||
// 上传成功回调
|
||||
xhr.onload = () => {
|
||||
if (xhr.status === 200 || xhr.status === 201) {
|
||||
console.log(xhr.responseText);
|
||||
onSuccess(xhr.responseText, file);
|
||||
let res = JSON.parse(xhr.responseText)
|
||||
|
||||
if(formData.value.desc_files){
|
||||
formData.value.desc_files += ",";
|
||||
}else{
|
||||
formData.value.desc_files = ""
|
||||
}
|
||||
formData.value.desc_files += res.file_path;
|
||||
|
||||
} else {
|
||||
onError(new Error(`Upload failed with status ${xhr.status}`));
|
||||
}
|
||||
};
|
||||
// 上传错误回调
|
||||
xhr.onerror = () => {
|
||||
onError(new Error('Network error'));
|
||||
};
|
||||
// 发送请求到服务器
|
||||
xhr.open('POST', baseURL+'/upload');
|
||||
xhr.send(formData1);
|
||||
};
|
||||
dayjs.locale('zh-cn')
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="table-operations">
|
||||
<a-row :gutter="20" >
|
||||
<a-col :span="22" :align="'end'" offset="2">
|
||||
<a-form v-model:value="searchParams" layout="inline">
|
||||
<a-form-item label="验证时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchParams.params.verify_time"
|
||||
valueFormat="YYYY-MM-DD HH:mm:ss"
|
||||
:locale="zhCN.DatePicker"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
<a-form-item label="关键字">
|
||||
<a-input v-model:value="searchParams.params.value" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="验证结果">
|
||||
<a-radio-group v-model:value="searchParams.params.verify_status">
|
||||
<a-radio-button value="success">正常</a-radio-button>
|
||||
<a-radio-button value="faild">异常</a-radio-button>
|
||||
<a-radio-button value="all">全部</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" :icon="h(SearchOutlined)" @click="getList">搜索</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
<a-table :columns="columns" :data-source="data" :row-selection="rowSelection" >
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'verify_status'">
|
||||
<a-tag v-if="record.verify_status === 'success'" color="green">正常</a-tag>
|
||||
<a-tag v-if="record.verify_status === 'fail'" color="red">异常</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'" >
|
||||
<a-button type="primary" v-if="record.status ==='yes'" @click="handleOpen(record)">备注</a-button>
|
||||
<a-button type="primary" v-if="record.status ==='no'" @click="handleOpen(record)">入账</a-button>
|
||||
</template>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<a-row >
|
||||
<a-col :span="8">
|
||||
<a-image-preview-group v-if="record.desc_files">
|
||||
<a-image :width="200" v-for="file in record.desc_files.split(',')" :src="baseURL+'/'+file"/>
|
||||
</a-image-preview-group>
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-typography-title :level="4" >备注</a-typography-title>
|
||||
<a-typography-paragraph>
|
||||
<blockquote>{{ record.desc }}</blockquote>
|
||||
</a-typography-paragraph>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<template #expandColumnTitle>
|
||||
<FileSearchOutlined />
|
||||
</template>
|
||||
</a-table>
|
||||
<a-modal v-model:open="openModal" :title="modalTitle" :confirm-loading="confirmLoading" @ok="handleOk">
|
||||
<a-form v-model:value="formData" >
|
||||
<a-form-item label="是否已报销">
|
||||
<a-switch v-model:checked="formData.status"></a-switch>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<a-textarea v-model:value="formData.desc" :rows="6"></a-textarea>
|
||||
</a-form-item>
|
||||
<a-form-item label="附件">
|
||||
<div class="clearfix">
|
||||
<a-upload
|
||||
v-model:file-list="fileList"
|
||||
:customRequest="handleUpload"
|
||||
@change="handleFileChange"
|
||||
@remove="handleFileRemove"
|
||||
list-type="picture-card"
|
||||
@preview="handlePreview"
|
||||
>
|
||||
<div>
|
||||
<PlusOutlined />
|
||||
<div style="margin-top: 8px">上传</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
<a-modal :open="previewVisible" :title="previewTitle" :footer="null" @cancel="handleCancelPreview">
|
||||
<img alt="example" style="width: 100%" :src="previewImage" />
|
||||
</a-modal>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
<context-holder />
|
||||
</template>
|
||||
<style scoped>
|
||||
.table-operations {
|
||||
margin-bottom: 16px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.table-operations > button {
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
/*max-width: 1280px;*/
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// export const baseURL = "http://127.0.0.1:5555"
|
||||
export const baseURL = "http://101.200.148.149:8555"
|
||||
@@ -0,0 +1,12 @@
|
||||
/* request.ts 这里博主用的是 TypeScript */
|
||||
|
||||
// 引入 Axios 库的功能,AxiosInstance 是 Axios 中自带的接口类型
|
||||
import axios, {type AxiosInstance} from 'axios';
|
||||
import {baseURL} from "./baseurl.ts";
|
||||
const service: AxiosInstance = axios.create({
|
||||
baseURL: baseURL,
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
// 导出 axios 实例
|
||||
export default service;
|
||||
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api/': {
|
||||
changeOrigin: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'#': '/src'
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: '../../alibabacloud_sample/templates',
|
||||
emptyOutDir: true,
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user