Compare commits

..

No commits in common. 'master' and 'v1.0.0' have entirely different histories.

  1. 9
      .gitignore
  2. 207
      README.md
  3. 102
      doc/docker.md
  4. BIN
      doc/images/img_1.png
  5. BIN
      doc/images/img_10.png
  6. BIN
      doc/images/img_11.png
  7. BIN
      doc/images/img_2.png
  8. BIN
      doc/images/img_3.png
  9. BIN
      doc/images/img_4.png
  10. BIN
      doc/images/img_5.png
  11. BIN
      doc/images/img_6.png
  12. BIN
      doc/images/img_7.png
  13. BIN
      doc/images/img_8.png
  14. BIN
      doc/images/img_9.png
  15. 166
      doc/local.md
  16. 67
      docker/build/docker-compose.yml
  17. 31
      docker/flyapps/docker-compose.only.yml
  18. 48
      docker/flyapps/docker-compose.yml
  19. 7
      docker/init/clean.sh
  20. 10
      docker/init/download.images
  21. 24
      docker/init/init.sh
  22. 1
      docker/init/timezone
  23. 43
      docker/mariadb/docker-compose.yml
  24. 108
      docker/mariadb/server.cnf
  25. 25
      docker/redis/docker-compose.yml
  26. 6
      docker/scripts/build.sh
  27. 9
      docker/scripts/start_all.sh
  28. 10
      docker/scripts/stop_all.sh
  29. 3
      fir_admin/.env.development
  30. 2
      fir_admin/.env.production
  31. 40
      fir_admin/src/api/app.js
  32. 54
      fir_admin/src/api/developer.js
  33. 10
      fir_admin/src/api/devices.js
  34. 31
      fir_admin/src/api/domain.js
  35. 26
      fir_admin/src/api/order.js
  36. 31
      fir_admin/src/api/report.js
  37. 13
      fir_admin/src/api/storage.js
  38. 8
      fir_admin/src/api/user.js
  39. 31
      fir_admin/src/api/wxbind.js
  40. 153
      fir_admin/src/router/index.js
  41. 2
      fir_admin/src/store/modules/user.js
  42. 15
      fir_admin/src/views/appinfos/AppDetail.vue
  43. 20
      fir_admin/src/views/appinfos/AppReleaseDetail.vue
  44. 41
      fir_admin/src/views/appinfos/AppReleaseList.vue
  45. 48
      fir_admin/src/views/appinfos/list.vue
  46. 4
      fir_admin/src/views/authentication/list.vue
  47. 146
      fir_admin/src/views/domain/Detail.vue
  48. 188
      fir_admin/src/views/domain/list.vue
  49. 2
      fir_admin/src/views/login/index.vue
  50. 15
      fir_admin/src/views/order/Detail.vue
  51. 38
      fir_admin/src/views/order/list.vue
  52. 158
      fir_admin/src/views/report/Detail.vue
  53. 227
      fir_admin/src/views/report/list.vue
  54. 15
      fir_admin/src/views/storage/Detail.vue
  55. 29
      fir_admin/src/views/storage/list.vue
  56. 183
      fir_admin/src/views/supersign/Detail.vue
  57. 117
      fir_admin/src/views/supersign/bill/add.vue
  58. 202
      fir_admin/src/views/supersign/bill/list.vue
  59. 193
      fir_admin/src/views/supersign/developer/Detail.vue
  60. 218
      fir_admin/src/views/supersign/developer/list.vue
  61. 3
      fir_admin/src/views/supersign/index.vue
  62. 98
      fir_admin/src/views/supersign/list.vue
  63. 127
      fir_admin/src/views/userinfos/UserDetail.vue
  64. 36
      fir_admin/src/views/userinfos/list.vue
  65. 157
      fir_admin/src/views/wxbind/Detail.vue
  66. 192
      fir_admin/src/views/wxbind/list.vue
  67. 47
      fir_client/README.md
  68. 11281
      fir_client/package-lock.json
  69. 11
      fir_client/package.json
  70. 16
      fir_client/public/config.js
  71. BIN
      fir_client/public/favicon.ico
  72. 7
      fir_client/public/index.html
  73. 8
      fir_client/public/short.html
  74. 154
      fir_client/src/App.vue
  75. 128
      fir_client/src/Download.vue
  76. BIN
      fir_client/src/assets/github.png
  77. BIN
      fir_client/src/assets/imgs/banner1.png
  78. BIN
      fir_client/src/assets/imgs/banner2.jpg
  79. BIN
      fir_client/src/assets/imgs/banner_1.jpg
  80. BIN
      fir_client/src/assets/imgs/banner_2.jpg
  81. BIN
      fir_client/src/assets/imgs/circle.png
  82. BIN
      fir_client/src/assets/imgs/computer.png
  83. BIN
      fir_client/src/assets/imgs/contact_us_bg.jpg
  84. BIN
      fir_client/src/assets/imgs/qq.png
  85. BIN
      fir_client/src/assets/imgs/service1.jpg
  86. BIN
      fir_client/src/assets/imgs/service2.jpg
  87. BIN
      fir_client/src/assets/imgs/service3.jpg
  88. BIN
      fir_client/src/assets/imgs/service4.jpg
  89. BIN
      fir_client/src/assets/imgs/skill.png
  90. BIN
      fir_client/src/assets/imgs/tel.png
  91. BIN
      fir_client/src/assets/imgs/twitter.png
  92. BIN
      fir_client/src/assets/imgs/weibo.png
  93. BIN
      fir_client/src/assets/imgs/weixin.png
  94. BIN
      fir_client/src/assets/sign/help/sign_help_0.png
  95. BIN
      fir_client/src/assets/sign/help/sign_help_1.png
  96. BIN
      fir_client/src/assets/sign/help/sign_help_2.png
  97. BIN
      fir_client/src/assets/sign/help/sign_help_3.png
  98. BIN
      fir_client/src/assets/sign/help/sign_help_4.png
  99. BIN
      fir_client/src/assets/sign/help/sign_help_5.png
  100. BIN
      fir_client/src/assets/wx_pay.jpg
  101. Some files were not shown because too many files have changed in this diff Show More

9
.gitignore vendored

@ -5,12 +5,3 @@ fir_ser/venv/
fir_client/.idea
fir_admin/.idea/.gitignore
fir_admin/.idea/
fir_client/yarn.lock
fir_download/package-lock.json
fir_client/src/assets/down_left.png
fir_client/src/assets/down_right.png
mailhtml/build/
mailhtml/node_modules/
mailhtml/.idea/
fir_client/dist_index/
fir_client/dist_short/

@ -1,63 +1,144 @@
### 用与应用分发,苹果超级签名
#### 部署前准备
- 备案域名【至少需要一个域名,以下可通过子域名部署】
- API域名
- 前端web域名
- 下载页域名
- 下载页域名可配置多个
- 存储域名(使用阿里云oss存储)
- ssl证书
- API域名证书
- 存储域名证书(使用阿里云oss存储)
- 前端web域名证书(可选)
- Centos8Stream 服务器
- 如果使用oss存储,则带宽为1M,若使用本地存储,则带宽越大越好
- 如果使用超级签,最低配置为2cpu 4G内存,若干不使用签名,则1cpu2G就行
- 阿里云短信或极光短信服务【可选一个,主要用与注册,重置密码】
- 阿里云短信
- 极光短信
- 邮箱服务【可选,用与注册,重置密码,通知信息】
- 阿里云OSS存储【可选】
- [sts授权配置](https://help.aliyun.com/document_detail/100624.html)
- 阿里云CDN【可选,用与加速访问】
- 极验验证【可选,滑动验证服务】
- 微信公众号【可选,用与微信扫描登录】
- 阿里云支付【可选,用与购买下载次数】
- 微信支付【可选,用与购买下载次数】
#### 自用搭建建议
- 阿里云服务器需要1cpu 2G内存,无需系统盘,如果使用超级签,可以适当增加配置
- 需要阿里云OSS存储和阿里云CDN,并且OSS存储和阿里云服务器部署同一个地区
- 可以申请一个极验进行滑动验证,或者开启验证码验证
- 阿里云备案域名:api和前端可以使用一个域名,下载页单独域名
#### 部署必备资料
- 域名证书
- web域名和证书
- api域名和证书
- 下载页域名(可配置证书)
- 存储域名和证书
- 本地存储,则该域名和证书可以和api域名证书一致
- 阿里云oss存储
- 开启cdn,需要新域名和证书
- 不开启,无需域名和证书
- Centos8Stream 服务器
## 部署方式
### 1.[Docker 部署](./doc/docker.md) 【推荐】
### 2.[本地部署](./doc/local.md)
### 功能预览
![img_1.png](./doc/images/img_1.png)
![img_2.png](./doc/images/img_2.png)
![img_3.png](./doc/images/img_3.png)
![img_4.png](./doc/images/img_4.png)
![img_5.png](./doc/images/img_5.png)
![img_6.png](./doc/images/img_6.png)
![img_7.png](./doc/images/img_7.png)
![img_8.png](./doc/images/img_8.png)
![img_9.png](./doc/images/img_9.png)
![img_10.png](./doc/images/img_10.png)
![img_11.png](./doc/images/img_11.png)
##### 从git上面下载源码
```
cd /data/
git clone https://github.com/nineaiyu/FlyApps
```
#### 超级签名依赖部署
##### fastlane [相关文档](https://github.com/fastlane/fastlane/blob/master/spaceship/docs/DeveloperPortal.md)
```
# fastlane
#1 gem ruby安装
yum install gem -y
yum install ruby-devel -y
yum install gcc gcc-c++ -y
#gem修改为国内镜像
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/
gem sources -l
#https://gems.ruby-china.com
#确保只有 gems.ruby-china.com
#2 更新rubby
gpg2 --keyserver hkp://pool.sks-keyservers.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
#如果脚本执行失败,可以这样
# echo 199.232.68.133 raw.githubusercontent.com >> /etc/hosts
curl -L get.rvm.io | bash -s stable
source /etc/profile.d/rvm.sh
rvm list known
rvm install 2.7
#3 安装fastlane
gem install fastlane
gem install pry
# space 好像有点问题,需要把这个注释掉
vim /usr/local/rvm/gems/ruby-2.7.0/gems/fastlane-2.146.1/spaceship/lib/spaceship/base.rb +298
#v.gsub!("\n", "\n\t") # to align nested elements #注释掉,要不然会报错
```
##### isign
```
# python2.7
yum update python -y
pip install -U setuptools pip
wget https://github.com/nineaiyu/isign/archive/v1.8.0.tar.gz
tar xvf v1.8.0.tar.gz
cd isign-1.8.0/
python setup.py build
python setup.py install
```
### FlyApp 服务器搭建 [python3 环境]
##### 搭建python env 环境
```
yum install python36 python36-devel redis mariadb-server mariadb-devel -y
python3 -m venv py3
source py3/bin/activate
```
###### 安装pip包
```
cd FlyApps/fir_ser/
pip install -r requirements.txt
```
###### 配置数据库
```
在 settings.py 配置redis 和 mysql 数据库
```
###### 迁移数据库
```
#如果是mysql,需要做一下配置,如果是sqlite,需要升级sqlite
#记得根据配置创建数据库
#create database flyapp default character set utf8 COLLATE utf8_general_ci;
#grant all on flyapp.* to flyuser@'127.0.0.1' identified by 'flypwd00oo.1';
python manage.py makemigrations
python manage.py migrate
```
### FlyApp web端搭建
###### npm编译环境
```
yum install npm
npm install -g n
n latest
npm install -g yarn
```
###### 编译web端
```
cd FlyApps/fir_client/
vim src/restful/index.js #修改api接口地址
yarn install
yarn build
```
###### 编译下载页面
```
cd FlyApps/fir_download/
vim src/restful/download.js #修改api接口地址
yarn install
yarn build
```
###### web目录操作
```
# web目录: /www/wwwroot/fly.dvcloud.xin/
cd /www/wwwroot/fly.dvcloud.xin/
cp -a /data/FlyApps/fir_client/dist/* .
cp -a /data/FlyApps/fir_download/dist/* .
```
##### 更新sqllite
```
tar xvf sqlite-autoconf-3310100.tar.gz
cd sqlite-autoconf-3310100
./configure --prefix=/usr/local/sqlite
make -j4
make install
rm -rf /usr/bin/sqlite3
ln -s /usr/local/sqlite/bin/sqlite3 /usr/bin/sqlite3
ll /usr/bin/sqlite3
echo "/usr/local/sqlite/lib" > /etc/ld.so.conf.d/sqlite3.conf
ldconfig
```

@ -1,102 +0,0 @@
### Docker最小化部署前准备,如果使用超级签和云存储,需要使用阿里云的服务器
系统|Centos 8 Stream 纯净系统 [2cpu 4G内存]
----|----
域名|```app.hehelucky.cn```
域名SSL证书| nginx 格式证书 [阿里云和腾讯云都可以申请免费ssl证书]
数据路径| ```/data```
### 开始部署
##### 1.从git上面下载源码
```
cd /data/
dnf install git -y
git clone https://github.com/nineaiyu/flyapps
```
## 注意,下面展示的相对路径,都是以```/data/flyapps```为相对目录
#### 2.docker环境安装
```
cd /data/flyapps/docker/init
sh init.sh
```
#### 3.配置域名和证书,如果有cdn或者oss,也要进行配置
a.将域名证书通过```sftp软件```或者```rz命令```复制到```/data/flyapps/nginx.conf.d```目录中
b.将域名证书分别重命名为 ```域名.pem``` 和 ```域名.key``` .本次使用的域名是 ```app.hehelucky.cn``` ,因此,证书名称类似如下
```shell script
nginx.conf.d/app.hehelucky.cn.key
nginx.conf.d/app.hehelucky.cn.pem
```
c.修改NGINX配置文件```nginx.conf.d/flyapps-vhost.conf```,将```server_name```字段修改为域名,将```ssl_certificate```和
```ssl_certificate_key```修改为对应证书路径,修改之后,对应字段如下
```shell
server_name app.hehelucky.cn;
ssl_certificate /etc/nginx/conf.d/app.hehelucky.cn.pem;
ssl_certificate_key /etc/nginx/conf.d/app.hehelucky.cn.key;
```
#### 4.配置api服务需要修改api和web域名,如果有需求,还可以配置 短信,邮箱,geetest,存储等信息
##### fir_ser配置文件 ```fir_ser/config.py```
```python
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
```
##### fir_client配置文件 ```fir_client/vue.confjg.js```
```javascript
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version,
};
```
##### 构建静态资源
```
cd /data/flyapps/docker/scripts
sh build.sh
```
##### 启动所有服务
```
cd /data/flyapps/docker/scripts
sh start_all.sh
```
##### 关闭所有服务
```
cd /data/flyapps/docker/scripts
sh stop_all.sh
```
##### 根据提示创建默认管理用户,用与访问管理后台
```shell
docker exec -it flyapps python manage.py createsuperuser
```
##### 测试访问
- 在浏览器输入自己配置的域名 https://app.hehelucky.cn/ 进行访问
- 管理后台访问 https://app.hehelucky.cn:3448/ 进行访问
##### 如果要使用本地的数据库和redis,api服务使用容器,需要修改文件```/data/flyapps/docker/flyapps/docker-compose.yml```
```shell
external_links:
- mariadb:mariadb
- redis:redis
```
修改为
```shell
extra_hosts:
- "mariadb:172.31.31.1"
- "redis:172.31.31.1"
```
并将```nginx:```下面的配置注释掉
同时还需要修改mariadb和redis授权

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

@ -1,166 +0,0 @@
### 本地部署前准备
系统 | Centos 8 Stream [2cpu 4G内存]
----|----
域名|```app.hehelucky.cn```
域名SSL证书| nginx 格式证书 [阿里云和腾讯云都可以申请免费ssl证书]
数据路径| ```/data```
##### 从git上面下载源码
```shell
cd /data/
dnf install git gcc zip unzip -y
git clone https://github.com/nineaiyu/flyapps
```
### flyapps 本地部署 [python3 环境]
##### 搭建python env 环境
```shell
dnf install python39 python39-devel redis mariadb-server mariadb-devel -y
python3.9 -m venv py39
source py39/bin/activate
```
###### 安装pip包
```shell
cd /data/flyapps/fir_ser/
pip install -U setuptools pip -i https://pypi.tuna.tsinghua.edu.cn/simple/
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
```
#### 配置数据库
##### 启动mariadb数据库
```shell
systemctl restart mariadb
systemctl enable mariadb
```
##### 创建mysql数据库
```shell
mysql
```
执行下面mysql命令
```mariadb
create database flyapps default character set utf8 COLLATE utf8_general_ci;
grant all on flyapps.* to flyuser@'127.0.0.1' identified by 'KGzKjZpWBp4R4RSa';
```
##### 启动redis数据库
```shell
echo 'requirepass nineven' >> /etc/redis.conf # 配置授权密码
systemctl restart redis
systemctl enable redis
```
### flyapps zsign ipa重签名工具安装
```shell
dnf install openssl-devel -y
dnf install gcc-c++ gcc -y
cd /data/flyapps/fir_ser/
tar xvf zsign-1.1.2.tar.gz
cd zsign-1.1.2/
g++ *.cpp common/*.cpp -lcrypto -O3 -std=c++11 -o /usr/bin/zsign
```
#### 配置api服务需要修改api和web域名,如果有需求,还可以配置 短信,邮箱,geetest,存储等信息
##### fir_ser配置文件 ```fir_ser/config.py```
```python
class DOMAINCONF(object):
API_DOMAIN = "https://app.hehelucky.cn" # 用与开启本地存储,上传应用配置
WEB_DOMAIN = "https://app.hehelucky.cn" # 用于超级签跳转配置,该域名一般为前端页面域名
```
##### fir_client配置文件 ```fir_client/vue.confjg.js```
```javascript
const pro_base_env = {
baseUrl: '/', //该选项可以填写web-api的域名,类似 https://api.xxx.com/
index_static: '/', //若配置cdn等加速,可以填写cdn加速域名
baseShortUrl: '/', //该选项可以填写short-api的域名,也可以和web-api域名一样,类似 https://api.xxx.com/
short_static: '/short/', //若配置cdn等加速,可以填写cdn加速域名
version: version,
};
```
### flyapps web端搭建
###### npm编译环境
```shell
dnf install npm -y
npm install -g n
n 12.13 # 安装12版本的node或者14版本 ,最新版本会有问题
npm install -g yarn
```
###### 编译web端和下载页
```shell
cd /data/flyapps/fir_client/
vim vue.config.js #修改api接口地址
# pro_base_env 正式环境信息
# dev_base_env 开发环境信息
yarn install
yarn build index # 前端打包,输出目录 dist_index
yarn build short # 下载页打包,输出目录 dist_short
```
###### 编译web管理后台
```shell
cd /data/flyapps/fir_admin/
yarn install
yarn build:prod # 下载页打包,输出目录 dist
```
### nginx配置
##### 安装nginx
```shell
dnf install nginx -y
```
##### 将域名ssl证书解压到```/etc/nginx/conf.d/``` 目录中,并重命名为```域名.pem```,```域名.key```格式
##### 拷贝虚拟主机配置文件
```shell
cp /data/flyapps/nginx.conf.d/flyapps-vhost.conf /etc/nginx/conf.d/
```
1.创建web目录,并将打包好的web复制该目录
```shell
mkdir -pv /data/{fir_client/short,fir_admin}
cp -a /data/flyapps/fir_client/dist_index/* /data/fir_client/
cp -a /data/flyapps/fir_client/dist_short/* /data/fir_client/short/
cp -a /data/flyapps/fir_admin/dist/* /data/fir_admin/
chown nginx.nginx -R /data/{fir_client,fir_admin}
```
2.启动nginx服务
```shell
nginx -t
systemctl restart nginx
systemctl enable nginx
```
##### 添加redis和mariadb uwsgi本地解析
```shell
echo '127.0.0.1 mariadb redis flyapps' >> /etc/hosts
```
###### 迁移数据库
```shell
cd /data/flyapps/fir_ser/
python manage.py makemigrations
python manage.py migrate
```
#### 启动api服务
```shell
echo 'net.core.somaxconn=1024' >> /etc/sysctl.conf
sysctl -p
cd /data/flyapps/fir_ser/
python manage.py start all -u nginx -usm 1 -d
```
##### 根据提示创建默认管理用户,用与访问管理后台
```shell
python manage.py createsuperuser
```
- 需要输入用户名,邮箱和密码
- 用户名和密码用与登录管理后台
- 邮箱和密码用与登录前端web

@ -1,67 +0,0 @@
version: '3.2'
services:
buildflyapps:
image: 'flyapps'
build:
context: ../../fir_ser
dockerfile: Dockerfile
command:
- echo
- success
buildclient:
container_name: buildclient
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- bash
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build index && yarn build short && ([ ! -e /web/fir_client ] && \cp -a dist_index /web/fir_client || \cp -a dist_index/* /web/fir_client/) && ([ ! -e /web/fir_client/short ] && \cp -a dist_short /web/fir_client/short || \cp -a dist_short/* /web/fir_client/short/) && echo build success
buildindex:
container_name: buildindex
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- bash
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build index && ([ ! -e /web/fir_index ] && \cp -a dist_index /web/fir_index || \cp -a dist_index/* /web/fir_index/) && echo build success
buildshort:
container_name: buildshort
image: 'node:14.17.3'
working_dir: /fir_client
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_client:/fir_client
- ../../data/web/:/web
command:
- sh
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build short && ([ ! -e /web/fir_short ] && \cp -a dist_short /web/fir_short || \cp -a dist_short/* /web/fir_short/) && echo build success
buildadmin:
container_name: buildadmin
image: 'node:14.17.3'
working_dir: /fir_admin
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_admin:/fir_admin
- ../../data/web/:/web
command:
- sh
- -c
- yarn config set registry https://registry.npm.taobao.org && yarn install && yarn build:prod && ([ ! -e /web/fir_admin ] && \cp -a dist /web/fir_admin || \cp -a dist/* /web/fir_admin/) && echo build success

@ -1,31 +0,0 @@
version: '3'
networks:
flyapps:
external: true
name: flyapps
services:
flyapps:
container_name: flyapps
restart: always
image: 'nineven/flyapps'
working_dir: /data/fir_ser
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_ser:/data/fir_ser
- ../../data/flyapps/files:/data/fir_ser/files
- ../../data/logs/flyapps/:/data/fir_ser/logs
- ../../data/flyapps/supersign/:/data/fir_ser/supersign
- ../../nginx.conf.d:/data/cert
networks:
flyapps:
ipv4_address: 172.31.31.100
external_links:
- mariadb:mariadb
- redis:redis
sysctls:
- net.core.somaxconn=4096
extra_hosts:
- "mariadb:172.31.31.1"
- "redis:172.31.31.1"

@ -1,48 +0,0 @@
version: '3'
networks:
flyapps:
external: true
name: flyapps
services:
flyapps:
container_name: flyapps
restart: always
image: 'nineven/flyapps'
working_dir: /data/fir_ser
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../fir_ser:/data/fir_ser
- ../../data/flyapps/files:/data/fir_ser/files
- ../../data/logs/flyapps/:/data/fir_ser/logs
- ../../data/flyapps/supersign/:/data/fir_ser/supersign
- ../../nginx.conf.d:/data/cert
networks:
flyapps:
ipv4_address: 172.31.31.100
external_links:
- mariadb:mariadb
- redis:redis
sysctls:
- net.core.somaxconn=4096
nginx:
container_name: nginx
restart: always
image: 'nginx:1.21.3'
volumes:
- /etc/localtime:/etc/localtime:ro
- ../../data/web:/data
- ../../nginx.conf.d:/etc/nginx/conf.d
- ../../data/logs/nginx:/var/log/nginx
networks:
flyapps:
ipv4_address: 172.31.31.200
ports:
- 80:80
- 443:443
- 3448:3448
external_links:
- flyapps:flyapps
depends_on:
- flyapps

@ -1,7 +0,0 @@
#!/bin/bash
#
#
for i in nginx flyapps mariadb redis buildclient buildshort buildadmin ;do echo $i;docker rm -f $i;done
docker network rm flyapps

@ -1,10 +0,0 @@
#!/bin/bash
#
#
docker pull 'bitnami/mariadb:10.7.3'
docker pull 'bitnami/redis:6.2.7'
docker pull 'nginx:1.21.3'
docker pull 'nineven/flyapps:latest'
docker pull 'node:14.17.3'

@ -1,24 +0,0 @@
#!/bin/bash
#
#
which dockerd
if [ $? -ne 0 ];then
dnf config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
dnf install docker-ce -y
fi
#which docker-compose
#if [ $? -ne 0 ];then
# curl -L https://get.daocloud.io/docker/compose/releases/download/v2.5.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
#fi
data_path="$(dirname $(dirname `pwd`))/data"
mkdir -pv ${data_path}/{flyapps/files,web,mariadb,redis,logs/{mariadb,nginx,flyapps}}
\cp $(dirname $(dirname `pwd`))/fir_ser/files/head_img.jpeg ${data_path}/flyapps/files/
chown 1001.1001 -R ${data_path}/{flyapps,web,mariadb,redis,logs/{mariadb,nginx,flyapps}}
chown 101.101 -R ${data_path}/{flyapps,logs/flyapps}
systemctl start docker && docker network create flyapps --driver bridge --subnet=172.31.31.0/24 --gateway=172.31.31.1
systemctl enable docker
systemctl status docker

@ -1 +0,0 @@
Asia/Shanghai

@ -1,43 +0,0 @@
version: '3.2'
networks:
flyapps:
external: true
name: flyapps
services:
mariadb:
image: bitnami/mariadb:10.7.3
container_name: mariadb
restart: always
environment:
- MARIADB_ROOT_PASSWORD=rootIPD.xx2.19
- MARIADB_DATABASE=flyapps
- MARIADB_USER=flyuser
- MARIADB_PASSWORD=KGzKjZpWBp4R4RSa
#- ALLOW_EMPTY_PASSWORD=yes
- MARIADB_ENABLE_SLOW_QUERY=1
- MARIADB_LONG_QUERY_TIME=3
- MARIADB_SKIP_TEST_DB=yes
- MARIADB_EXTRA_FLAGS=--max-connect-errors=3000 --max_connections=30000
ports:
- 3306:3306
networks:
flyapps:
ipv4_address: 172.31.31.90
volumes:
- ../init/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ../../data/mariadb:/bitnami/mariadb/data
- ../../data/logs/mariadb:/data/logs/mariadb
- ./server.cnf:/opt/bitnami/mariadb/conf/my_custom.cnf:ro
# adminer:
# image: adminer
# ports:
# - 8080:8080
# networks:
# - fungame

@ -1,108 +0,0 @@
#
# These groups are read by MariaDB server.
# Use it for options that only the server (but not clients) should see
#
# See the examples of server my.cnf files in /usr/share/mysql/
#
# this is only for the mysqld standalone daemon
[mysqld]
binlog_cache_size = 192K
thread_stack = 384K
join_buffer_size = 4096K
query_cache_type = 1
max_heap_table_size = 1024M
default_storage_engine = InnoDB
performance_schema_max_table_instances = 400
table_definition_cache = 400
skip-external-locking
key_buffer_size = 512M
max_allowed_packet = 1G
table_open_cache = 1024
sort_buffer_size = 2048K
net_buffer_length = 4K
read_buffer_size = 2048K
read_rnd_buffer_size = 1024K
myisam_sort_buffer_size = 16M
thread_cache_size = 192
query_cache_size = 256M
tmp_table_size = 1024M
sql-mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
max_connections = 30000
max_connect_errors = 1000
open_files_limit = 65535
expire_logs_days = 10
#log_queries_not_using_indexes=on
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
init_connect='SET NAMES utf8mb4'
#character-set-server=utf8
skip_name_resolve
event_scheduler=1
#skip-grant-tables
#innodb_data_home_dir = /var/lib/mysql/
#innodb_data_file_path = ibdata1:10M:autoextend
#innodb_log_group_home_dir = /var/lib/mysql/
#innodb_buffer_pool_size = 1024M
#innodb_log_file_size = 128M
#innodb_log_buffer_size = 32M
#innodb_flush_log_at_trx_commit = 1
#innodb_lock_wait_timeout = 50
#innodb_max_dirty_pages_pct = 90
#innodb_read_io_threads = 4
#innodb_write_io_threads = 4
#
# Allow server to accept connections on all interfaces.
#
#bind-address=0.0.0.0
#
# this is only for embedded server
# This group is only read by MariaDB servers, not by MySQL.
# If you use the same .cnf file for MySQL and MariaDB,
# you can put MariaDB-only options here
[mariadb]
#autoset_open_files_limit
#enable_slow_query_log
#audit
plugin_load_add=server_audit
server_audit_logging=on
server_audit_events=connect,query
server_audit=force_plus_permanent
server_audit_events=QUERY_DDL,QUERY_DML,CONNECT
server_audit_output_type=file
server_audit_file_rotate_now=on
server_audit_file_rotations=9
server_audit_file_rotate_size=1G
server_audit_file_path=/data/logs/mariadb
log_error=/data/logs/mariadb/mariadb.err.log
log_output=FILE
slow_query_log
long_query_time=3
slow_query_log_file=/data/logs/mariadb/mariadb-slow.log
log_queries_not_using_indexes=ON #Logging Queries That Don't Use Indexes
#server_id=2
log-bin=/data/logs/mariadb/mysql-bin
# This group is only read by MariaDB-10.5 servers.
# If you use the same .cnf file for MariaDB of different versions,
# use this group for options that older servers don't understand

@ -1,25 +0,0 @@
version: '3.2'
networks:
flyapps:
external: true
name: flyapps
services:
redis:
image: 'bitnami/redis:6.2.7'
container_name: redis
restart: always
volumes:
- ../init/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- ../../data/redis:/bitnami/redis/data
environment:
#- REDIS_REPLICATION_MODE=master
#- ALLOW_EMPTY_PASSWORD=yes
- REDIS_PASSWORD=nineven
networks:
- flyapps
#ports:
# - 6379:6379

@ -1,6 +0,0 @@
#!/bin/bash
#
#
cd ../build/
docker compose up buildclient buildadmin

@ -1,9 +0,0 @@
#!/bin/bash
#
#
cd ../mariadb/ && docker compose up -d
cd ../redis/ && docker compose up -d
cd ../flyapps/ && docker compose up -d
docker logs -f flyapps

@ -1,10 +0,0 @@
#!/bin/bash
#
#
cd ../flyapps/ && docker compose down
cd ../redis/ && docker compose down
cd ../mariadb/ && docker compose down

@ -2,5 +2,4 @@
ENV = 'development'
# base api
VUE_APP_BASE_API = 'https://flyapps.cn/api/v3/fir/server'
#VUE_APP_BASE_API = 'https://app.hehelucky.cn/api/v3/fir/server'
VUE_APP_BASE_API = 'https://app.hehelucky.cn/api/v3/fir/server'

@ -2,5 +2,5 @@
ENV = 'production'
# base api
VUE_APP_BASE_API = '/api/v3/fir/server'
VUE_APP_BASE_API = '/prod-api'

@ -1,6 +1,6 @@
import request from '@/utils/request'
export function getAppList(query) {
export function getAppInfos(query) {
return request({
url: '/app/info',
method: 'get',
@ -8,29 +8,23 @@ export function getAppList(query) {
})
}
export function getAppInfos(pk) {
export function updateAppInfo(data) {
return request({
url: '/app/info/' + pk,
method: 'get'
})
}
export function updateAppInfo(pk, data) {
return request({
url: '/app/info/' + pk,
url: '/app/info',
method: 'put',
data
})
}
export function deleteApp(pk) {
export function deleteApp(data) {
return request({
url: '/app/info/' + pk,
method: 'delete'
url: '/app/info',
method: 'delete',
data
})
}
export function getAppReleaseList(query) {
export function getAppReleaseInfos(query) {
return request({
url: '/app/release/info',
method: 'get',
@ -38,24 +32,10 @@ export function getAppReleaseList(query) {
})
}
export function getAppReleaseInfos(pk) {
return request({
url: '/app/release/info/' + pk,
method: 'get'
})
}
export function updateReleaseAppInfo(pk, data) {
return request({
url: '/app/release/info/' + pk,
method: 'put',
data
})
}
export function downloadAppReleaseInfos(data) {
export function updateReleaseAppInfo(data) {
return request({
url: '/app/release/info',
method: 'post',
method: 'put',
data
})
}

@ -1,54 +0,0 @@
import request from '@/utils/request'
export function getDeveloperList(query) {
return request({
url: '/developer/info',
method: 'get',
params: query
})
}
export function getDeveloperInfo(pk) {
return request({
url: '/developer/info/' + pk,
method: 'get'
})
}
export function updatedeveloperInfo(pk, data) {
return request({
url: '/developer/info/' + pk,
method: 'put',
data
})
}
export function getBillList(query) {
return request({
url: '/bill/info',
method: 'get',
params: query
})
}
export function addBillInfo(data) {
return request({
url: '/bill/info',
method: 'post',
data
})
}
export function delBillInfo(pk) {
return request({
url: '/bill/info/' + pk,
method: 'delete'
})
}
export function getUserBillInfo(query) {
return request({
url: '/bill/userinfo',
method: 'get',
params: query
})
}

@ -1,10 +0,0 @@
import request from '@/utils/request'
export function getDevicesList(query) {
return request({
url: '/devices/info',
method: 'get',
params: query
})
}

@ -1,31 +0,0 @@
import request from '@/utils/request'
export function getDomainList(query) {
return request({
url: '/domain/info',
method: 'get',
params: query
})
}
export function getDomainInfos(pk) {
return request({
url: '/domain/info/' + pk,
method: 'get'
})
}
export function updateDomainInfo(pk, data) {
return request({
url: '/domain/info/' + pk,
method: 'put',
data
})
}
export function deleteDomain(pk) {
return request({
url: '/domain/info/' + pk,
method: 'delete'
})
}

@ -1,6 +1,6 @@
import request from '@/utils/request'
export function getOrderList(query) {
export function getOrderInfo(query) {
return request({
url: '/order/info',
method: 'get',
@ -8,30 +8,10 @@ export function getOrderList(query) {
})
}
export function getOrderInfo(pk) {
return request({
url: '/order/info/' + pk,
method: 'get'
})
}
export function updateOrderInfo(pk, data) {
return request({
url: '/order/info/' + pk,
method: 'put',
data
})
}
export function deleteOrderInfo(pk) {
return request({
url: '/order/info/' + pk,
method: 'delete'
})
}
export function createOrderInfo(data) {
export function updateOrderInfo(data) {
return request({
url: '/order/info',
method: 'post',
method: 'put',
data
})
}

@ -1,31 +0,0 @@
import request from '@/utils/request'
export function getAppReportList(query) {
return request({
url: '/report/info',
method: 'get',
params: query
})
}
export function getAppReportIfo(pk) {
return request({
url: '/report/info/' + pk,
method: 'get'
})
}
export function updateAppReportIfo(pk, data) {
return request({
url: '/report/info/' + pk,
method: 'put',
data
})
}
export function deleteAppReportIfo(pk) {
return request({
url: '/report/info/' + pk,
method: 'delete'
})
}

@ -1,6 +1,6 @@
import request from '@/utils/request'
export function getStorageList(query) {
export function getStorageInfo(query) {
return request({
url: '/storage/info',
method: 'get',
@ -8,16 +8,9 @@ export function getStorageList(query) {
})
}
export function getStorageInfo(pk) {
export function updateStorageInfo(data) {
return request({
url: '/storage/info/' + pk,
method: 'get'
})
}
export function updateStorageInfo(pk, data) {
return request({
url: '/storage/info/' + pk,
url: '/storage/info',
method: 'put',
data
})

@ -53,11 +53,3 @@ export function updateCertificationInfo(data) {
data
})
}
export function resetPassword(data) {
return request({
url: '/user/info',
method: 'post',
data
})
}

@ -1,31 +0,0 @@
import request from '@/utils/request'
export function getWxBindList(query) {
return request({
url: '/wxbind/info',
method: 'get',
params: query
})
}
export function getWxBindInfos(pk) {
return request({
url: '/wxbind/info/' + pk,
method: 'get'
})
}
export function updateWxBindInfo(pk,data) {
return request({
url: '/wxbind/info/' + pk,
method: 'put',
data
})
}
export function deleteWxBind(pk) {
return request({
url: '/wxbind/info/' + pk,
method: 'delete'
})
}

@ -3,8 +3,6 @@ import Router from 'vue-router'
Vue.use(Router)
const celery_flower = process.env.VUE_APP_BASE_API.split('/')
/* Layout */
import Layout from '@/layout'
@ -139,6 +137,18 @@ export const constantRoutes = [
hidden: true
}
]
},
{
path: '/sign',
component: Layout,
children: [
{
path: 'index',
name: 'index',
component: () => import('@/views/form/index'),
meta: { title: '超级签名', icon: 'form' }
}
]
}, {
path: '/authentication',
component: Layout,
@ -157,8 +167,7 @@ export const constantRoutes = [
hidden: true
}
]
},
{
}, {
path: '/order',
component: Layout,
children: [
@ -177,131 +186,6 @@ export const constantRoutes = [
}
]
},
{
path: '/domain',
component: Layout,
children: [
{
path: 'list',
name: 'domain_info_list',
component: () => import('@/views/domain/list'),
meta: { title: '下载域名管理', icon: 'form' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/domain/Detail'),
name: 'domain_info_edit',
meta: { title: '编辑信息', noCache: true, activeMenu: '/domain/list' },
hidden: true
}
]
},
{
path: '/report',
component: Layout,
children: [
{
path: 'list',
name: 'report_info_list',
component: () => import('@/views/report/list'),
meta: { title: '应用举报信息', icon: 'form' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/report/Detail'),
name: 'report_info_edit',
meta: { title: '编辑信息', noCache: true, activeMenu: '/report/list' },
hidden: true
}
]
},
{
path: '/wxbind',
component: Layout,
children: [
{
path: 'list',
name: 'wxbind_info_list',
component: () => import('@/views/wxbind/list'),
meta: { title: '微信绑定管理', icon: 'form' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/wxbind/Detail'),
name: 'wxbind_info_edit',
meta: { title: '编辑信息', noCache: true, activeMenu: '/wxbind/list' },
hidden: true
}
]
},
{
path: '/supersign',
component: Layout,
redirect: '/supersign/developer',
name: '超级签名',
meta: {
title: '超级签名',
icon: 'nested'
},
children: [
{
path: 'developer',
name: 'developer',
redirect: '/supersign/developer/list',
component: () => import('@/views/supersign/index'),
children: [
{
path: 'list',
name: 'developer_user_info_list',
component: () => import('@/views/supersign/developer/list'),
meta: { title: '苹果开发者', icon: 'form' }
},
{
path: 'edit/:id(\\d+)',
component: () => import('@/views/supersign/developer/Detail'),
name: 'developer_user_info_edit',
meta: { title: '编辑信息', noCache: true, activeMenu: '/supersign/list' },
hidden: true
}
]
},
{
path: 'devices',
name: 'devices',
redirect: '/supersign/devices/list',
component: () => import('@/views/supersign/index'),
children: [
{
path: 'list',
name: 'devices_info_list',
component: () => import('@/views/supersign/devices/list'),
meta: { title: '设备消耗', icon: 'form' }
}
]
},
{
path: 'bill',
name: 'bill',
redirect: '/supersign/bill/list',
component: () => import('@/views/supersign/index'),
meta: { title: '设备账单', icon: 'form' },
children: [
{
path: 'list',
name: 'bill_info_list',
component: () => import('@/views/supersign/bill/list'),
meta: { title: '消耗账单', icon: 'form' }
},
{
path: 'add',
name: 'bill_info_add',
component: () => import('@/views/supersign/bill/add'),
meta: { title: '设备充值', icon: 'form' }
}
]
}
]
},
{
path: '/settings',
component: Layout,
@ -347,6 +231,7 @@ export const constantRoutes = [
}
]
},
{
path: '/nested',
component: Layout,
@ -415,16 +300,6 @@ export const constantRoutes = [
}
]
},
{
path: 'celery-flower-link',
component: Layout,
children: [
{
path: celery_flower[0] + '//' + celery_flower[2] + '/flower',
meta: { title: '任务监控', icon: 'link' }
}
]
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }

@ -1,7 +1,7 @@
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { resetRouter } from '@/router'
const Base64 = require('js-base64').Base64
const Base64 = require('js-base64').Base64;
const getDefaultState = () => {
return {

@ -264,30 +264,29 @@ export default {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
id: ''
is_edit: false
}
},
computed: {
},
created() {
if (this.isEdit) {
this.id = this.$route.params && this.$route.params.id
this.fetchData(this.id)
const id = this.$route.params && this.$route.params.id
this.fetchData(id)
}
},
methods: {
fetchData(id) {
getAppInfos(id).then(response => {
if (response.data) {
this.postForm = response.data
getAppInfos({ id: id }).then(response => {
if (response.data.length === 1) {
this.postForm = response.data[0]
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateAppInfo(this.id, this.postForm).then(response => {
updateAppInfo(this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {

@ -102,7 +102,7 @@
<el-form-item label="ios内测版 udid" label-width="200px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-if="postForm.udid" :value="JSON.stringify(postForm.udid)" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" disabled />
<el-input v-model="postForm.udid" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" disabled />
</el-col>
</el-row>
</el-form-item>
@ -161,28 +161,28 @@ export default {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
id: ''
is_edit: false
}
},
computed: {
},
created() {
this.id = this.$route.params && this.$route.params.id
this.fetchData(this.id)
const id = this.$route.params && this.$route.params.id
const app_id = this.$route.params && this.$route.params.app_id
this.fetchData(id, app_id)
},
methods: {
fetchData(id) {
getAppReleaseInfos(id).then(response => {
if (response.data) {
this.postForm = response.data
fetchData(id, app_id) {
getAppReleaseInfos({ id: id, app_id: app_id }).then(response => {
if (response.data.length === 1) {
this.postForm = response.data[0]
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateReleaseAppInfo(this.id, this.postForm).then(response => {
updateReleaseAppInfo(this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {

@ -2,7 +2,7 @@
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.release_id" placeholder="release_id" style="width: 250px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
@ -67,16 +67,13 @@
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="200" class-name="small-padding fixed-width">
<el-table-column label="操作" align="center" width="100" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'app_release_info_edit',params:{app_id:listQuery.app_id,id:scope.row.id}}">
<el-button type="primary" size="mini">
查看编辑
</el-button>
</router-link>
<el-button type="info" size="mini" @click="download_bin(scope.row)">
下载源文件
</el-button>
</template>
</el-table-column>
</el-table>
@ -86,7 +83,7 @@
</template>
<script>
import { downloadAppReleaseInfos, getAppReleaseList } from '@/api/app'
import { getAppReleaseInfos } from '@/api/app'
import { baseFilter } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
@ -133,11 +130,12 @@ export default {
page: 1,
limit: 10,
app_id: undefined,
download_token: undefined,
release_id: undefined,
ordering: '-created_time'
sort: '-created_time'
},
sortOptions
sortOptions,
type_choices: [],
status_choices: []
}
},
created() {
@ -145,30 +143,19 @@ export default {
this.fetchData()
},
methods: {
download_bin(app_info) {
downloadAppReleaseInfos(
{
'token': app_info.download_token,
'release_id': app_info.release_id,
'app_id': app_info.app_id
}
).then(res => {
if (res.code === 1000) {
window.location.href = res.data.download_url
} else {
this.$message.error(res.msg)
}
})
},
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getAppReleaseList(this.listQuery).then(response => {
this.list = response.data.results
this.total = response.data.count
getAppReleaseInfos(this.listQuery).then(response => {
this.list = response.data
if (this.list && this.list.length > 0) {
this.type_choices = this.list[0].type_choices
this.status_choices = this.list[0].status_choices
}
this.total = response.total
this.listLoading = false
})
}

@ -4,7 +4,7 @@
<el-input v-model="listQuery.bundle_id" placeholder="Bundle_Id" style="width: 250px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.name" placeholder="应用名称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.short" placeholder="短连接" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<!-- <el-input v-model="listQuery.domain_name" placeholder="应用专属访问域名" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />-->
<el-input v-model="listQuery.domain_name" placeholder="应用专属访问域名" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-if="type_choices" v-model="listQuery.type" placeholder="应用类型" clearable class="filter-item" style="width: 120px" @change="handleFilter">
<el-option v-for="item in type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
@ -12,7 +12,7 @@
<el-option v-for="item in status_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-input v-model="listQuery.user_id" placeholder="用户ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
@ -40,11 +40,7 @@
</el-table-column>
<el-table-column label="应用名称">
<template slot-scope="scope">
<el-tooltip content="点击访问下载页">
<el-link :underline="false" @click="go_download_url(scope.row)">
{{ scope.row.name }}
</el-link>
</el-tooltip>
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="Bundle_Id" align="center">
@ -75,26 +71,16 @@
{{ scope.row.short }}
</template>
</el-table-column>
<el-table-column label="下载次数" width="80" align="center">
<el-table-column label="下载次数" width="100" align="center">
<template slot-scope="scope">
{{ scope.row.count_hits }}
</template>
</el-table-column>
<el-table-column label="当天下载次数" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.today_hits_count }}
</template>
</el-table-column>
<el-table-column label="应用大小" width="80" align="center">
<template slot-scope="scope">
{{ scope.row.master_release.binary_size }}
</template>
</el-table-column>
<el-table-column label="应用类型" width="80" align="center">
<template slot-scope="scope">
{{ scope.row |appTypeNameFilter }}
</template>
</el-table-column>
<el-table-column label="历史版本数" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'app_release_info_list',params:{app_id:scope.row.id}}">
@ -135,13 +121,13 @@
</template>
</el-table-column>
</el-table>
<pagination v-show="count>0" :total="count" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
</div>
</template>
<script>
import { deleteApp, getAppList } from '@/api/app'
import { getAppInfos, deleteApp } from '@/api/app'
import { baseFilter } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
@ -171,10 +157,7 @@ export default {
return statusMap[status]
},
appStatusNameFilter(row) {
return baseFilter(row.status, row.status_choices)
},
appTypeNameFilter(row) {
return baseFilter(row.type, row.type_choices)
return baseFilter(row.status,row.status_choices)
},
appStatusFilter(status) {
const statusMap = {
@ -189,13 +172,13 @@ export default {
return {
list: null,
listLoading: true,
count: 0,
total: 0,
listQuery: {
page: 1,
limit: 10,
name: undefined,
bundle_id: undefined,
ordering: '-count_hits',
sort: '-count_hits',
type: undefined,
domain_name: undefined,
user_id: undefined
@ -206,14 +189,9 @@ export default {
}
},
created() {
this.listQuery.user_id = this.$route.params && this.$route.params.user_id
this.listQuery.id = this.$route.params && this.$route.params.app_id
this.fetchData()
},
methods: {
go_download_url(app_info) {
window.open(app_info.preview_url, '_blank', '')
},
deleteApp(app_id) {
this.$confirm('此操作将永久删除该应用, 是否继续?', '提示', {
confirmButtonText: '确定',
@ -221,7 +199,7 @@ export default {
type: 'warning'
}).then(() => {
this.listLoading = true
deleteApp(app_id).then(() => {
deleteApp({ id: app_id }).then(response => {
this.$message.success('删除成功')
this.fetchData()
this.listLoading = false
@ -239,13 +217,13 @@ export default {
},
fetchData() {
this.listLoading = true
getAppList(this.listQuery).then(response => {
this.list = response.data.results
getAppInfos(this.listQuery).then(response => {
this.list = response.data
if (this.list && this.list.length > 0) {
this.type_choices = this.list[0].type_choices
this.status_choices = this.list[0].status_choices
}
this.count = response.data.count
this.total = response.total
this.listLoading = false
})
}

@ -86,7 +86,9 @@
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="deleteApp(scope.row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>

@ -1,146 +0,0 @@
<template>
<div class="app-container">
<el-form ref="postForm" :model="postForm" label-width="100px" :disabled="!is_edit">
<el-row>
<el-col :span="12">
<el-form-item label="用户ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.user_id" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item v-if="postForm.app_info&& postForm.app_info.app_id" label="应用ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.app_id" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item v-if="postForm.app_info&& postForm.app_info.app_id" label="应用名称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.app_info.name" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="域名类型">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.domain_type" class="filter-item" disabled>
<el-option v-for="item in postForm.domain_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="绑定的域名" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.domain_name" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="绑定状态" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.is_enable" class="filter-item">
<el-option v-for="item in domain_state_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="CnameID" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.cname_id" disabled />
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-button v-if="!is_edit" type="primary" :disabled="postForm.id === postForm.used_id" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
<el-button type="primary" @click="updateData">保存修改</el-button>
</div>
</el-col>
</div>
</template>
<script>
import { getDomainInfos, updateDomainInfo } from '@/api/domain'
const domain_state_choices = [
{ id: false, name: '继续绑定' },
{ id: true, name: '绑定成功' }
]
const defaultForm = {
user_id: undefined,
app_info: undefined,
domain_type: undefined,
domain_name: undefined,
cname_id: undefined,
is_enable: undefined,
domain_type_choices: undefined,
created_time: undefined
}
export default {
name: 'OrderDetail',
components: { }, filters: {},
data() {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
domain_state_choices,
id: ''
}
},
computed: {},
created() {
this.id = this.$route.params && this.$route.params.id
this.fetchData(this.id)
},
methods: {
fetchData(id) {
getDomainInfos(id).then(response => {
if (response.data) {
this.postForm = response.data
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateDomainInfo(this.id, this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style scoped>
.editor-container {
position: relative;
height: 100%;
}
</style>

@ -1,188 +0,0 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.user_id" placeholder="用户ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.domain_name" placeholder="下载域名" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.app_name" placeholder="应用名称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.domain_type" placeholder="下载域名类型" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in domain_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.is_enable" placeholder="域名绑定状态" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in domain_state_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
Search
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
stripe
>
<el-table-column align="center" label="ID" width="90">
<template slot-scope="scope">
{{ scope.row.id }}
</template>
</el-table-column>
<el-table-column label="用户ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'user_info_edit',params:{id:scope.row.user_id}}">
<el-link type="primary"> {{ scope.row.user_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="下载域名" min-width="280px" align="center">
<template slot-scope="scope">
{{ scope.row.domain_name }}
</template>
</el-table-column>
<el-table-column class-name="status-col" label="下载域名类型" width="160" align="center">
<template slot-scope="scope">
<router-link v-if="scope.row.domain_type===2" :to="{name: 'app_info_list',params:{user_id:scope.row.user_id,app_id:scope.row.app_id}}">
<el-tag :type="scope.row.domain_type | domainStatusFilter">{{ scope.row| domainLableFilter }}</el-tag>
</router-link>
<el-tag v-else :type="scope.row.domain_type | domainStatusFilter">{{ scope.row| domainLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="绑定状态" width="95" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.is_enable | statusFilter">{{ scope.row| statusLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="创建时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.created_time">
<span>{{ scope.row.created_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'domain_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini">
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="remove_domain(scope.row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
</div>
</template>
<script>
import { deleteDomain, getDomainList } from '@/api/domain'
import { baseFilter } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' }
]
const domain_state_choices = [
{ id: false, name: '继续绑定' },
{ id: true, name: '绑定成功' }
]
export default {
name: 'DomainInfo',
components: { Pagination },
directives: { waves },
filters: {
formatTime(time) {
if (time) {
return time.split('T')[0]
}
},
domainStatusFilter(status) {
const statusMap = {
'1': 'success',
'0': 'gray',
'2': 'info' }
return statusMap[status]
},
domainLableFilter(row) {
if (row.domain_type === 2) {
return '应用' + row.app_info.name + '域名'
}
return baseFilter(row.domain_type, row.domain_type_choices)
},
statusLableFilter(row) {
return baseFilter(row.is_enable, domain_state_choices)
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
page: 1,
limit: 10,
user_id: undefined,
ordering: '-created_time',
domain_name: undefined,
app_name: undefined,
domain_type: undefined,
is_enable: undefined
},
sortOptions,
domain_type_choices: [],
domain_state_choices
}
},
created() {
this.fetchData()
}, mounted() {
if (this.$route.params.user_id) {
this.listQuery.user_id = this.$route.params.user_id
}
console.log(this.$router)
},
methods: {
remove_domain(domain_id) {
deleteDomain(domain_id).then(response => {
this.fetchData()
})
},
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getDomainList(this.listQuery).then(response => {
this.list = response.data.results
if (this.list && this.list.length > 0) {
this.domain_type_choices = this.list[0].domain_type_choices
}
this.total = response.data.count
this.listLoading = false
})
}
}
}
</script>

@ -66,7 +66,7 @@ export default {
}
}
const validatePassword = (rule, value, callback) => {
if (value.length < 3) {
if (value.length < 6) {
callback(new Error('The password can not be less than 6 digits'))
} else {
callback()

@ -142,27 +142,26 @@ export default {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
id: ''
is_edit: false
}
},
computed: {},
created() {
this.id = this.$route.params && this.$route.params.id
this.fetchData(this.id)
const id = this.$route.params && this.$route.params.id
this.fetchData(id)
},
methods: {
fetchData(id) {
getOrderInfo(id).then(response => {
if (response.data) {
this.postForm = response.data
getOrderInfo({ id: id }).then(response => {
if (response.data.length === 1) {
this.postForm = response.data[0]
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateOrderInfo(this.id, this.postForm).then(response => {
updateOrderInfo(this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {

@ -14,7 +14,7 @@
<el-select v-model="listQuery.order_type" placeholder="订单类型" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in order_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
@ -59,7 +59,7 @@
</el-table-column>
<el-table-column label="实付金额" align="center" width="80px">
<template slot-scope="scope">
{{ scope.row.actual_amount/100 }}
{{ scope.row.actual_amount }}
</template>
</el-table-column>
<el-table-column label="购买数量" align="center" width="90px">
@ -105,7 +105,7 @@
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="remove_order_info(scope.row)">
<el-button type="danger" size="mini" @click="deleteApp(scope.row.id)">
删除
</el-button>
</template>
@ -117,18 +117,14 @@
</template>
<script>
import { deleteOrderInfo, getOrderList } from '@/api/order'
import { getOrderInfo } from '@/api/order'
import { baseFilter } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' },
{ label: '付款时间 Ascending', key: 'pay_time' },
{ label: '付款时间 Descending', key: '-pay_time' },
{ label: '付款金额 Ascending', key: 'actual_amount' },
{ label: '付款金额 Descending', key: '-actual_amount' }
{ label: '创建时间 Ascending', key: 'reviewed_time' },
{ label: '创建时间 Descending', key: '-reviewed_time' },
]
export default {
@ -192,7 +188,7 @@ export default {
page: 1,
limit: 10,
user_id: undefined,
ordering: '-created_time',
sort: '-created_time',
payment_type: undefined,
payment_name: undefined,
payment_number: undefined,
@ -209,35 +205,25 @@ export default {
created() {
this.fetchData()
}, mounted() {
if (this.$route.params.user_id) {
this.listQuery.user_id = this.$route.params.user_id
if (this.$route.query.user_id) {
this.listQuery.user_id = this.$route.query.user_id
}
},
methods: {
remove_order_info(order_info) {
deleteOrderInfo(order_info.id).then(response => {
this.list = response.data
if (response.code === 1000) {
this.fetchData()
} else {
this.$message.error('操作失败了 ' + response.msg)
}
})
},
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getOrderList(this.listQuery).then(response => {
this.list = response.data.results
getOrderInfo(this.listQuery).then(response => {
this.list = response.data
if (this.list && this.list.length > 0) {
this.payment_type_choices = this.list[0].payment_type_choices
this.status_choices = this.list[0].status_choices
this.order_type_choices = this.list[0].order_type_choices
}
this.total = response.data.count
this.total = response.total
this.listLoading = false
})
}

@ -1,158 +0,0 @@
<template>
<div class="app-container">
<el-form ref="postForm" :model="postForm" label-width="100px" :disabled="!is_edit">
<el-row>
<el-col :span="12">
<el-form-item label="应用ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.app_id" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="应用名称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.app_name" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="举报类型">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.report_type" class="filter-item" disabled>
<el-option v-for="item in postForm.report_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="举报原因">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.report_reason" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" disabled />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="举报者姓名" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.username" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="举报联系方式" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.email" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="举报者IP地址" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.remote_addr" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="处理状态" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.status" class="filter-item">
<el-option v-for="item in postForm.status_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="处理备注" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.description" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" />
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-button v-if="!is_edit" type="primary" :disabled="postForm.id === postForm.used_id" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
<el-button type="primary" @click="updateData">保存修改</el-button>
</div>
</el-col>
</div>
</template>
<script>
import { getAppReportIfo, updateAppReportIfo } from '@/api/report'
const defaultForm = {
app_id: undefined,
app_name: undefined,
bundle_id: undefined,
created_time: undefined,
description: undefined,
email: undefined,
remote_addr: undefined,
report_reason: undefined,
status: undefined,
report_type: undefined,
username: undefined
}
export default {
name: 'ReportDetail',
components: { }, filters: {},
data() {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
id: ''
}
},
computed: {},
created() {
this.id = this.$route.params && this.$route.params.id
this.fetchData(this.id)
},
methods: {
fetchData(id) {
getAppReportIfo(this.id).then(response => {
if (response.data) {
this.postForm = response.data
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateAppReportIfo(this.id, this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style scoped>
.editor-container {
position: relative;
height: 100%;
}
</style>

@ -1,227 +0,0 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.app_id" placeholder="应用ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.app_name" placeholder="应用名称" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.bundle_id" placeholder="Bundle_Id" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.remote_addr" placeholder="远端地址" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.email" placeholder="联系信息" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.report_type" placeholder="举报类型" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in report_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.status" placeholder="处理状态" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in status_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
Search
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
stripe
>
<el-table-column align="center" label="ID" width="90">
<template slot-scope="scope">
{{ scope.row.id }}
</template>
</el-table-column>
<el-table-column label="应用ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'app_info_edit',params:{id:scope.row.app_id}}">
<el-link type="primary"> {{ scope.row.app_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="应用名称" width="80px">
<template slot-scope="scope">
{{ scope.row.app_name }}
</template>
</el-table-column>
<el-table-column label="Bundle_Id" align="center">
<template slot-scope="scope">
<span>{{ scope.row.bundle_id }}</span>
</template>
</el-table-column>
<el-table-column label="联系信息" align="center">
<template slot-scope="scope">
<span>{{ scope.row.email }}</span>
</template>
</el-table-column>
<el-table-column label="联系姓名" align="center">
<template slot-scope="scope">
<span>{{ scope.row.username }}</span>
</template>
</el-table-column>
<el-table-column label="举报者IP" align="center">
<template slot-scope="scope">
<span>{{ scope.row.remote_addr }}</span>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="举报类型" width="95" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.report_type | payStatusFilter">{{ scope.row| payLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="处理状态" width="95" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.status | certStatusFilter">{{ scope.row| statusLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="提交时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.created_time">
<span>{{ scope.row.created_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'report_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini">
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="remove_order_info(scope.row)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
</div>
</template>
<script>
import { deleteAppReportIfo, getAppReportList } from '@/api/report'
import { baseFilter } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' }
]
export default {
name: 'OrderInfo',
components: { Pagination },
directives: { waves },
filters: {
formatTime(time) {
if (time) {
return time.split('T')[0]
}
},
payStatusFilter(status) {
const statusMap = {
'0': 'gray',
'1': 'success' }
return statusMap[status]
},
certStatusFilter(status) {
const statusMap = {
'0': 'success',
'1': 'info',
'2': 'gray' }
return statusMap[status]
},
payLableFilter(row) {
return baseFilter(row.report_type, row.report_type_choices)
},
statusLableFilter(row) {
return baseFilter(row.status, row.status_choices)
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
},
appStatusNameFilter(row) {
for (const r of row.status_choices) {
if (r.id === row.status) {
return r.name
}
}
},
appStatusFilter(status) {
const statusMap = {
'0': 'danger',
'1': 'success',
'2': 'gray'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
page: 1,
limit: 10,
app_id: undefined,
ordering: '-created_time',
report_type: undefined,
app_name: undefined,
bundle_id: undefined,
email: undefined,
status: undefined,
remote_addr: undefined,
username: undefined
},
sortOptions,
report_type_choices: [],
status_choices: []
}
},
created() {
this.fetchData()
}, mounted() {
if (this.$route.params.user_id) {
this.listQuery.user_id = this.$route.params.user_id
}
},
methods: {
remove_order_info(order_info) {
deleteAppReportIfo(order_info.id).then(response => {
this.list = response.data
if (response.code === 1000) {
this.fetchData()
} else {
this.$message.error('操作失败了 ' + response.msg)
}
})
},
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getAppReportList(this.listQuery).then(response => {
this.list = response.data.results
if (this.list && this.list.length > 0) {
this.report_type_choices = this.list[0].report_type_choices
this.status_choices = this.list[0].status_choices
}
this.total = response.data.count
this.listLoading = false
})
}
}
}
</script>

@ -147,27 +147,26 @@ export default {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
id: ''
is_edit: false
}
},
computed: {},
created() {
this.id = this.$route.params && this.$route.params.id
this.fetchData(this.id)
const id = this.$route.params && this.$route.params.id
this.fetchData(id)
},
methods: {
fetchData(id) {
getStorageInfo(id).then(response => {
if (response.data) {
this.postForm = response.data
getStorageInfo({ id: id }).then(response => {
if (response.data.length === 1) {
this.postForm = response.data[0]
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateStorageInfo(this.id, this.postForm).then(response => {
updateStorageInfo(this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {

@ -9,7 +9,7 @@
<el-select v-model="listQuery.storage_type" placeholder="存储类型" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in storage_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
@ -83,7 +83,9 @@
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="deleteApp(scope.row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
@ -93,15 +95,15 @@
</template>
<script>
import { getStorageList } from '@/api/storage'
import { getStorageInfo } from '@/api/storage'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' },
{ label: '更新时间 Ascending', key: 'updated_time' },
{ label: '更新时间 Descending', key: '-updated_time' }
{ label: '创建时间 Ascending', key: 'reviewed_time' },
{ label: '创建时间 Descending', key: '-reviewed_time' },
{ label: '更新时间 Ascending', key: 'created_time' },
{ label: '更新时间 Descending', key: '-created_time' }
]
export default {
@ -160,21 +162,21 @@ export default {
page: 1,
limit: 10,
name: undefined,
sort: '-created_time',
bucket_name: undefined,
access_key: undefined,
storage_type: undefined,
domain_name: undefined,
user_id: undefined,
ordering: '-created_time'
used_id: undefined
},
sortOptions,
storage_choices: []
}
},
created() {
this.listQuery.user_id = this.$route.params && this.$route.params.user_id
this.fetchData()
}, mounted() {
},mounted() {
if (this.$route.query.user_id) {
this.listQuery.user_id = this.$route.query.user_id
}
@ -186,13 +188,12 @@ export default {
},
fetchData() {
this.listLoading = true
getStorageList(this.listQuery).then(response => {
this.list = response.data.results
console.log(this.list)
getStorageInfo(this.listQuery).then(response => {
this.list = response.data
if (this.list && this.list.length > 0) {
this.storage_choices = this.list[0].storage_choices
}
this.total = response.data.count
this.total = response.total
this.listLoading = false
})
}

@ -0,0 +1,183 @@
<template>
<div class="app-container">
<el-form ref="postForm" :model="postForm" label-width="100px" :disabled="!is_edit">
<el-row>
<el-col :span="12">
<el-form-item label="用户ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.user_id" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="存储名称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.name" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="存储类型">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.storage_type" class="filter-item" disabled>
<el-option v-for="item in postForm.storage_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="更新时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.updated_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="存储描述信息">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.description" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="存储访问access_key" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.access_key" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="存储访问secret_key" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.secret_key" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="存储空间bucket_name" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.bucket_name" />
</el-col>
</el-row>
</el-form-item>
<div v-if="postForm.storage_type===2">
<el-form-item label="阿里云sts_role_arn" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.sts_role_arn" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="阿里云endpoint" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.endpoint" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="阿里云下载授权方式" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.download_auth_type" class="filter-item" style="width: 100%">
<el-option v-for="item in postForm.download_auth_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item v-if="postForm.download_auth_type===2" label="阿里云cnd_auth_key" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.cnd_auth_key" />
</el-col>
</el-row>
</el-form-item>
</div>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-button v-if="!is_edit" type="primary" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
<el-button type="primary" @click="updateData">保存修改</el-button>
</div>
</el-col>
</div>
</template>
<script>
import { getStorageInfo, updateStorageInfo } from '@/api/storage'
const defaultForm = {
user_id: undefined,
name: undefined,
storage_type: undefined,
access_key: undefined,
secret_key: undefined,
bucket_name: undefined,
domain_name: undefined,
created_time: undefined,
description: undefined,
sts_role_arn: undefined,
endpoint: undefined,
download_auth_type: undefined,
cnd_auth_key: undefined,
updated_time: undefined,
download_auth_type_choices: []
}
export default {
name: 'StorageDetail',
components: { }, filters: {},
data() {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false
}
},
computed: {},
created() {
const id = this.$route.params && this.$route.params.id
this.fetchData(id)
},
methods: {
fetchData(id) {
getStorageInfo({ id: id }).then(response => {
if (response.data.length === 1) {
this.postForm = response.data[0]
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateStorageInfo(this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style scoped>
.editor-container {
position: relative;
height: 100%;
}
</style>

@ -1,117 +0,0 @@
<template>
<div class="app-container">
<el-form ref="postForm" :model="postForm" label-width="100px">
<el-row>
<el-col :span="12">
<el-form-item label="用户ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.user_id" placeholder="邮箱,手机号,或者uid" />
</el-col>
<el-col :span="4">
<el-button @click="check_balance('user_id')">查询校验</el-button>
</el-col>
</el-row>
<el-row v-if="user_info.private_balance_info.all_balance+user_info.public_balance_info.all_balance">
<el-col>
<el-link :underline="false"> 公有池数量 {{ user_info.public_balance_info.all_balance }} 已经使用数量 {{ user_info.public_balance_info.used_balance }}</el-link>
</el-col>
<el-col>
<el-link :underline="false"> 私有池数量 {{ user_info.private_balance_info.all_balance }} 已经使用数量 {{ user_info.private_balance_info.used_balance }}</el-link>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="目标用户">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.to_user_id" placeholder="邮箱,手机号,或者uid" />
</el-col>
<el-col :span="4">
<el-button @click="check_balance('to_user_id')">查询校验</el-button>
</el-col>
</el-row>
<el-row v-if="to_user_info.private_balance_info.all_balance+to_user_info.public_balance_info.all_balance">
<el-col>
<el-link :underline="false"> 公有池数量 {{ to_user_info.public_balance_info.all_balance }} 已经使用数量 {{ to_user_info.public_balance_info.used_balance }}</el-link>
</el-col>
<el-col>
<el-link :underline="false"> 私有池数量 {{ to_user_info.private_balance_info.all_balance }} 已经使用数量 {{ to_user_info.private_balance_info.used_balance }}</el-link>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="转移数量">
<el-row :gutter="12">
<el-col :span="16">
<el-input-number v-model="postForm.number" :min="0" :max="10000" label="可使用设备数" />
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-button type="primary" @click="addbill">确定</el-button>
</el-col>
</div>
</template>
<script>
import waves from '@/directive/waves'
import { addBillInfo, getUserBillInfo } from '@/api/developer'
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' }
]
export default {
name: 'BillAddInfo',
directives: { waves },
filters: {
formatTime(time) {
return time.split('T')[0]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
postForm: {
to_user_id: undefined,
user_id: undefined,
number: undefined
},
user_info: { public_balance_info: { all_balance: undefined }, private_balance_info: { used_balance: undefined }},
to_user_info: { public_balance_info: { all_balance: undefined }, private_balance_info: { used_balance: undefined }},
action_choices: undefined,
sortOptions
}
},
created() {
},
methods: {
check_balance(act) {
let user_id = this.postForm.user_id
if (act === 'to_user_id') {
user_id = this.postForm.to_user_id
}
getUserBillInfo({ user_id: user_id }).then(response => {
if (act === 'user_id') {
this.user_info = response
} else if (act === 'to_user_id') {
this.to_user_info = response
}
console.log(this.user_info)
})
},
addbill() {
addBillInfo(this.postForm).then(response => {
this.$message.success('充值成功')
})
}
}
}
</script>

@ -1,202 +0,0 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.user_id" placeholder="用户ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.to_user_id" placeholder="目标用户" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.udid" placeholder="设备UDID" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.app_id" placeholder="应用id" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.action" placeholder="充值类型" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in action_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
Search
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
stripe
>
<el-table-column align="center" label="ID" width="90">
<template slot-scope="scope">
{{ scope.row.id }}
</template>
</el-table-column>
<el-table-column label="用户ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'user_info_edit',params:{id:scope.row.user_id}}">
<el-link type="primary"> {{ scope.row.user_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="目标用户ID" width="100" align="center">
<template slot-scope="scope">
<router-link v-if="scope.row.to_user_id" :to="{name: 'user_info_edit',params:{id:scope.row.to_user_id}}">
<el-link type="primary"> {{ scope.row.to_user_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="应用ID" width="100" align="center">
<template slot-scope="scope">
<router-link v-if="scope.row.app_id" :to="{name: 'app_info_edit',params:{id:scope.row.app_id}}">
<el-link type="primary"> {{ scope.row.app_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="UDID" align="center">
<template slot-scope="scope">
{{ scope.row.udid }}
</template>
</el-table-column>
<el-table-column label="客户端IP" align="center" >
<template slot-scope="scope">
{{ scope.row.remote_addr }}
</template>
</el-table-column>
<el-table-column label="账单类型" align="center" width="100">
<template slot-scope="scope">
{{ scope.row.action }}
</template>
</el-table-column>
<el-table-column label="备注" align="center">
<template slot-scope="scope">
{{ scope.row.description }}
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="创建时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.created_time">
<span>{{ scope.row.created_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button type="danger" size="mini" @click="delbill(scope.row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
</div>
</template>
<script>
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves'
import { delBillInfo, getBillList } from '@/api/developer'
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' }
]
export default {
name: 'BillInfo',
components: { Pagination },
directives: { waves },
filters: {
formatTime(time) {
return time.split('T')[0]
},
certStatusFilter(status) {
const statusMap = {
'0': 'danger',
'1': 'success',
'2': 'gray',
'3': 'info'
}
return statusMap[status]
},
certLableFilter(row) {
for (const v of row.auth_type_choices) {
if (v.id === row.auth_type) {
return v.name
}
}
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
},
appStatusNameFilter(row) {
for (const r of row.status_choices) {
if (r.id === row.status) {
return r.name
}
}
},
appStatusFilter(status) {
const statusMap = {
'0': 'danger',
'1': 'success',
'2': 'gray'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
page: 1,
limit: 10,
ordering: '-created_time',
action: undefined,
app_id: undefined,
to_user_id: undefined,
user_id: undefined,
udid: undefined
},
action_choices: undefined,
sortOptions
}
},
created() {
this.listQuery.issuer_id = this.$route.params && this.$route.params.issuer_id
this.listQuery.user_id = this.$route.params && this.$route.params.user_id
this.fetchData()
},
methods: {
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
delbill(id) {
delBillInfo(id).then(response => {
this.$message.success('删除成功')
this.fetchData()
this.listLoading = false
})
},
fetchData() {
this.listLoading = true
getBillList(this.listQuery).then(response => {
this.list = response.data.results
if (this.list && this.list.length > 0) {
this.action_choices = this.list[0].action_choices
}
this.total = response.data.count
this.listLoading = false
})
}
}
}
</script>

@ -1,193 +0,0 @@
<template>
<div class="app-container">
<el-form ref="postForm" :model="postForm" label-width="100px" :disabled="!is_edit">
<el-row>
<el-col :span="12">
<el-form-item label="用户ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.user_id" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="issuer_id">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.issuer_id" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="可使用设备数">
<el-row :gutter="12">
<el-col :span="16">
<el-input-number v-model="postForm.usable_number" :min="0" :max="100" label="可使用设备数" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="已消耗设备数">
<el-row :gutter="12">
<el-col :span="16">
<el-input-number v-model="postForm.use_number" :min="0" :max="100" label="已消耗设备数" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="账户类型">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.auth_type" class="filter-item" disabled>
<el-option v-for="item in postForm.auth_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="更新时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.updated_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="描述信息">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.description" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="账户状态" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-tooltip :content="postForm.is_actived|userStatusFilter" placement="top">
<el-switch
v-model="postForm.is_actived"
active-color="#13ce66"
inactive-color="#ff4949"
:active-value="true"
:inactive-value="false"
/>
</el-tooltip>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="证书过期时间" prop="timestamp" label-width="160px">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.cert_expire_time" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="private_key_id" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.private_key_id" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="p8key" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.p8key" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="certid" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.certid" />
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-button v-if="!is_edit" type="primary" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
<el-button type="primary" @click="updateData">保存修改</el-button>
</div>
</el-col>
</div>
</template>
<script>
import { getDeveloperInfo, updatedeveloperInfo } from '@/api/developer'
const defaultForm = {
user_id: undefined,
is_actived: undefined,
issuer_id: undefined,
private_key_id: undefined,
p8key: undefined,
certid: undefined,
created_time: undefined,
description: undefined,
usable_number: undefined,
use_number: undefined,
auth_type: undefined,
cert_expire_time: undefined,
updated_time: undefined,
auth_type_choices: []
}
export default {
name: 'DeveloperDetail',
components: { }, filters: {
userStatusFilter(status) {
const statusMap = {
true: '激活,允许使用',
false: '禁用,禁止使用'
}
return statusMap[status]
}
},
data() {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
id: ''
}
},
computed: {},
created() {
this.id = this.$route.params && this.$route.params.id
this.fetchData(this.id)
},
methods: {
fetchData(id) {
getDeveloperInfo(id).then(response => {
if (response.data) {
this.postForm = response.data
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updatedeveloperInfo(this.id, this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style scoped>
.editor-container {
position: relative;
height: 100%;
}
</style>

@ -1,218 +0,0 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.user_id" placeholder="用户ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.issuer_id" placeholder="issuer_id" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.private_key_id" placeholder="private_key_id" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.certid" placeholder="证书ID" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.description" placeholder="备注" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.auth_type" placeholder="账户类型" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in auth_type_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
Search
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
stripe
>
<el-table-column align="center" label="ID" width="90">
<template slot-scope="scope">
{{ scope.row.id }}
</template>
</el-table-column>
<el-table-column label="用户ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'user_info_edit',params:{id:scope.row.user_id}}">
<el-link type="primary"> {{ scope.row.user_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="issuer_id" width="300">
<template slot-scope="scope">
{{ scope.row.issuer_id }}
</template>
</el-table-column>
<el-table-column label="private_key_id" align="center">
<template slot-scope="scope">
<span>{{ scope.row.private_key_id }}</span>
</template>
</el-table-column>
<el-table-column label="certid" align="center">
<template slot-scope="scope">
{{ scope.row.certid }}
</template>
</el-table-column>
<el-table-column label="可用设备数" align="center" width="100">
<template slot-scope="scope">
{{ scope.row.usable_number }}
</template>
</el-table-column>
<el-table-column label="消耗设备数" align="center" width="100">
<template slot-scope="scope">
<router-link :to="{name: 'devices_info_list',params:{issuer_id:scope.row.issuer_id}}">
<el-link type="primary"> {{ scope.row.use_number }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="账户类型" width="130" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.auth_type | certStatusFilter">{{ scope.row| certLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="是否激活" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.is_actived | statusFilter">{{ scope.row |StatusNameFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="创建时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.created_time">
<span>{{ scope.row.created_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="证书过期时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.updated_time">
<span>{{ scope.row.cert_expire_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'developer_user_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini">
查看编辑
</el-button>
</router-link>
<!-- <el-button type="danger" size="mini" @click="deleteApp(scope.row.id)">-->
<!-- 删除-->
<!-- </el-button>-->
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
</div>
</template>
<script>
import { getDeveloperList } from '@/api/developer'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' },
{ label: '更新时间 Ascending', key: 'updated_time' },
{ label: '更新时间 Descending', key: '-updated_time' },
{ label: '证书过期时间 Ascending', key: 'cert_expire_time' },
{ label: '证书过期时间 Descending', key: '-cert_expire_time' }
]
export default {
name: 'StorageInfo',
components: { Pagination },
directives: { waves },
filters: {
formatTime(time) {
return time.split('T')[0]
},
certStatusFilter(status) {
const statusMap = {
'0': 'danger',
'1': 'success',
'2': 'gray',
'3': 'info'
}
return statusMap[status]
},
certLableFilter(row) {
for (const v of row.auth_type_choices) {
if (v.id === row.auth_type) {
return v.name
}
}
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
},
StatusNameFilter(row) {
for (const r of row.status_choices) {
if (r.id === row.status) {
return r.name
}
}
},
appStatusFilter(status) {
const statusMap = {
'0': 'danger',
'1': 'success',
'2': 'gray'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
page: 1,
limit: 10,
ordering: '-created_time',
issuer_id: undefined,
private_key_id: undefined,
certid: undefined,
description: undefined,
user_id: undefined,
auth_type: undefined
},
sortOptions,
auth_type_choices: [],
status_choices: []
}
},
created() {
this.fetchData()
},
methods: {
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getDeveloperList(this.listQuery).then(response => {
this.list = response.data.results
if (this.list && this.list.length > 0) {
this.auth_type_choices = this.list[0].auth_type_choices
this.status_choices = this.list[0].status_choices
}
this.total = response.data.count
this.listLoading = false
})
}
}
}
</script>

@ -1,3 +0,0 @@
<template>
<router-view />
</template>

@ -2,13 +2,14 @@
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.user_id" placeholder="用户ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.issuer_id" placeholder="开发者issuer_id" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.udid" placeholder="设备UDID" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.name" placeholder="应用名称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.bundle_id" placeholder="BundleID" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.short" placeholder="短连接" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-input v-model="listQuery.bucket_name" placeholder="bucket_name" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.access_key" placeholder="access_key" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.name" placeholder="存储名称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.domain_name" placeholder="下载域名" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.storage_type" placeholder="存储类型" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in storage_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.sort" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
@ -36,41 +37,31 @@
</router-link>
</template>
</el-table-column>
<el-table-column label="应用ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'app_info_edit',params:{id:scope.row.app_id}}">
<el-link type="primary"> {{ scope.row.app_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="开发者issuer_id" align="center">
<el-table-column label="存储名称">
<template slot-scope="scope">
<router-link :to="{name: 'developer_user_info_edit',params:{id:scope.row.developer_pk}}">
<el-link type="primary"> {{ scope.row.developer_id }}</el-link>
</router-link>
{{ scope.row.name }}
</template>
</el-table-column>
<el-table-column label="应用名称" align="center" >
<el-table-column label="存储访问access_key" align="center">
<template slot-scope="scope">
{{ scope.row.bundle_name }}
<span>{{ scope.row.access_key }}</span>
</template>
</el-table-column>
<el-table-column label="短连接" align="center" width="100">
<el-table-column label="存储空间bucket_name" align="center">
<template slot-scope="scope">
{{ scope.row.short }}
{{ scope.row.bucket_name }}
</template>
</el-table-column>
<el-table-column label="BundleID" align="center">
<el-table-column label="下载域名" align="center">
<template slot-scope="scope">
{{ scope.row.bundle_id }}
{{ scope.row.domain_name }}
</template>
</el-table-column>
<el-table-column label="设备UDID" align="center" width="350">
<el-table-column class-name="status-col" label="存储类型" width="95" align="center">
<template slot-scope="scope">
{{ scope.row.device_udid }}
<el-tag :type="scope.row.storage_type | certStatusFilter">{{ scope.row| certLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="创建时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
@ -79,9 +70,21 @@
</el-tooltip>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="更新时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.updated_time">
<span>{{ scope.row.updated_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'storage_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini">
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="deleteApp(scope.row.id)">
删除
</el-button>
@ -94,17 +97,19 @@
</template>
<script>
import { getDevicesList } from '@/api/devices'
import { getStorageInfo } from '@/api/storage'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' }
{ label: '创建时间 Ascending', key: 'reviewed_time' },
{ label: '创建时间 Descending', key: '-reviewed_time' },
{ label: '更新时间 Ascending', key: 'created_time' },
{ label: '更新时间 Descending', key: '-created_time' }
]
export default {
name: 'DevicesInfo',
name: 'StorageInfo',
components: { Pagination },
directives: { waves },
filters: {
@ -121,8 +126,8 @@ export default {
return statusMap[status]
},
certLableFilter(row) {
for (const v of row.auth_type_choices) {
if (v.id === row.auth_type) {
for (const v of row.storage_choices) {
if (v.id === row.storage_type) {
return v.name
}
}
@ -158,20 +163,19 @@ export default {
listQuery: {
page: 1,
limit: 10,
ordering: '-created_time',
issuer_id: undefined,
short: undefined,
bundle_id: undefined,
name: undefined,
user_id: undefined,
udid: undefined
sort: '-created_time',
bucket_name: undefined,
access_key: undefined,
storage_type: undefined,
domain_name: undefined,
user_id: undefined
},
sortOptions
sortOptions,
storage_choices: []
}
},
created() {
this.listQuery.issuer_id = this.$route.params && this.$route.params.issuer_id
this.listQuery.user_id = this.$route.params && this.$route.params.user_id
this.fetchData()
},
methods: {
@ -181,12 +185,12 @@ export default {
},
fetchData() {
this.listLoading = true
getDevicesList(this.listQuery).then(response => {
this.list = response.data.results
getStorageInfo(this.listQuery).then(response => {
this.list = response.data
if (this.list && this.list.length > 0) {
this.auth_type_choices = this.list[0].auth_type_choices
this.storage_choices = this.list[0].storage_choices
}
this.total = response.data.count
this.total = response.total
this.listLoading = false
})
}

@ -6,21 +6,21 @@
<el-form-item label="UID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.uid" disabled/>
<el-input :value="postForm.uid" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="账号" prop="username">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.username"/>
<el-input v-model="postForm.username" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="昵称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.first_name"/>
<el-input v-model="postForm.first_name" />
</el-col>
</el-row>
</el-form-item>
@ -28,9 +28,7 @@
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.gender" class="filter-item" placeholder="Please select">
<el-option v-for="item in postForm.gender_choices" :key="item.id" :label="item.name"
:value="item.id"
/>
<el-option v-for="item in postForm.gender_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
@ -39,7 +37,7 @@
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.role" class="filter-item" placeholder="Please select">
<el-option v-for="item in postForm.role_choices" :key="item.id" :label="item.name" :value="item.id"/>
<el-option v-for="item in postForm.role_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
@ -48,7 +46,7 @@
<el-form-item label="邮箱" prop="email">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.email"/>
<el-input v-model="postForm.email" />
</el-col>
</el-row>
</el-form-item>
@ -56,37 +54,35 @@
<el-form-item label="手机" prop="mobile">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.mobile"/>
<el-input v-model="postForm.mobile" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="Q Q" prop="number">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.qq"/>
<el-input v-model="postForm.qq" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="职位">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.job"/>
<el-input v-model="postForm.job" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="公司">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.company"/>
<el-input v-model="postForm.company" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="备注">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.memo" :autosize="{ minRows: 4, maxRows: 6}" type="textarea"
placeholder="Please input"
/>
<el-input v-model="postForm.memo" :autosize="{ minRows: 4, maxRows: 6}" type="textarea" placeholder="Please input" />
</el-col>
</el-row>
</el-form-item>
@ -95,9 +91,7 @@
<el-form-item label="用户头像">
<el-row :gutter="12">
<el-col :span="16">
<el-image :src="postForm.head_img" :preview-src-list="[postForm.head_img]" fit="contain"
style="width: 100px; height: 100px"
/>
<el-image :src="postForm.head_img" :preview-src-list="[postForm.head_img]" fit="contain" style="width: 100px; height: 100px" />
</el-col>
</el-row>
</el-form-item>
@ -183,16 +177,10 @@
<el-form-item label="实名认证">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.certification" class="filter-item" placeholder="Please select"
:disabled="postForm.certification === -1"
>
<el-option v-for="item in postForm.certification_status_choices" :key="item.id" :label="item.name"
:value="item.id"
/>
<el-select v-model="postForm.certification" class="filter-item" placeholder="Please select" :disabled="postForm.certification === -1">
<el-option v-for="item in postForm.certification_status_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<router-link v-if="postForm.certification_id"
:to="{name: 'user_authentication_info_edit',params:{id:postForm.certification_id.id}}"
>
<router-link v-if="postForm.certification_id" :to="{name: 'user_authentication_info_edit',params:{id:postForm.certification_id.id}}">
<el-button type="primary">
审核认证信息
</el-button>
@ -206,49 +194,38 @@
<el-form-item label="下载域名" prop="domain_name">
<el-row :gutter="12">
<el-col :span="16">
<router-link v-if="postForm.id" :to="{name: 'domain_info_list',params:{user_id:postForm.id}}">
<el-button type="primary">查看下载域名绑定信息</el-button>
</router-link>
<el-input v-model="postForm.domain_name" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="下载次数" prop="download_times">
<el-row :gutter="12">
<el-col :span="8">
<el-input :value="postForm.download_times" disabled/>
<el-input :value="postForm.download_times" disabled />
</el-col>
<el-col :span="8">
<router-link v-if="postForm.id" :to="{name: 'order_info_list',params:{user_id:postForm.id}}">
<el-button type="primary">查看充值订单信息</el-button>
</router-link>
<el-button>后台充值</el-button>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="注册时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.date_joined" type="datetime" disabled/>
<el-date-picker :value="postForm.date_joined" type="datetime" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="应用版本历史记录">
<el-row :gutter="20">
<el-col :span="8">
<el-input v-model="postForm.history_release_limit"/>
<el-input v-model="postForm.history_release_limit" />
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-col :span="8">
<el-button @click="add_amount_download_times(postForm.id)">下载次数充值</el-button>
</el-col>
<el-col :span="8">
<el-button @click="reset_password(postForm.id)">重置密码</el-button>
</el-col>
<el-button v-if="!is_edit" type="primary" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
@ -260,9 +237,9 @@
<script>
import { validURL } from '@/utils/validate'
import { getUserInfos, resetPassword, updateUserInfo } from '@/api/user'
import { createOrderInfo } from '@/api/order'
import { changeStorageInfo, getStorageList } from '@/api/storage'
import { getUserInfos, updateUserInfo } from '@/api/user'
import { getStorageInfo,changeStorageInfo } from '@/api/storage'
import { CommentDropdown, PlatformDropdown, SourceUrlDropdown } from './Dropdown'
const defaultForm = {
email: undefined,
@ -283,7 +260,6 @@ const defaultForm = {
date_joined: undefined,
storage_active: undefined,
storage: undefined,
certification_id: undefined,
supersign_active: undefined,
role_choices: [],
gender_choices: [],
@ -292,7 +268,7 @@ const defaultForm = {
export default {
name: 'UserDetail',
components: {}, filters: {
components: { CommentDropdown, PlatformDropdown, SourceUrlDropdown }, filters: {
userStatusFilter(status) {
const statusMap = {
true: '激活,允许登录',
@ -356,7 +332,8 @@ export default {
storage_selection: []
}
},
computed: {},
computed: {
},
created() {
if (this.isEdit) {
const id = this.$route.params && this.$route.params.id
@ -364,61 +341,17 @@ export default {
}
},
methods: {
add_amount_download_times(user_id) {
this.$prompt('请输入要充值的下载次数', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(({ value }) => {
createOrderInfo({ id: user_id, amount: parseInt(value) }).then(res => {
if (res.code === 1000) {
this.$message.success('下载次数充值成功')
this.fetchData(user_id)
}
})
}).catch((e) => {
console.log(e)
this.$message({
type: 'info',
message: '取消充值'
})
})
}, reset_password(user_id) {
this.$prompt('请输入新密码,密码至少6位', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(({ value }) => {
resetPassword({ user_id: user_id, password: value }).then(res => {
if (res.code === 1000) {
this.$message.success('密码重置成功')
this.fetchData(user_id)
} else {
this.$message.error(res.msg)
}
})
}).catch((e) => {
console.log(e)
this.$message({
type: 'info',
message: '取消充值'
})
})
},
changeStorageData(force) {
changeStorageInfo({
id: this.postForm.id,
use_storage_id: this.postForm.storage,
force: force
}).then(response => {
changeStorageInfo({id: this.postForm.id, use_storage_id: this.postForm.storage,force: force}).then(response => {
this.$message.success('存储数据迁移并设置刷新成功')
}).catch(err => {
console.log(err)
})
},
fetchStorageData(user_id) {
getStorageList({ user_id: user_id }).then(response => {
if (response.data.storage_selection) {
this.storage_selection = response.data.storage_selection
getStorageInfo({ user_id: user_id }).then(response => {
if (response.storage_selection) {
this.storage_selection = response.storage_selection
}
}).catch(err => {
console.log(err)

@ -34,12 +34,12 @@
<el-image :src="scope.row.head_img" :preview-src-list="[scope.row.head_img]" fit="contain" style="width: 80px; height: 80px" />
</template>
</el-table-column>
<el-table-column label="登录名" align="center">
<el-table-column label="登录名">
<template slot-scope="scope">
{{ scope.row.username }}
</template>
</el-table-column>
<el-table-column label="昵称" align="center">
<el-table-column label="昵称">
<template slot-scope="scope">
{{ scope.row.first_name }}
</template>
@ -49,7 +49,7 @@
<span>{{ scope.row.mobile }}</span>
</template>
</el-table-column>
<el-table-column label="邮箱" width="200" align="center">
<el-table-column label="邮箱" width="210" align="center">
<template slot-scope="scope">
{{ scope.row.email }}
</template>
@ -60,39 +60,21 @@
{{ scope.row.download_times }}
</template>
</el-table-column>
<el-table-column label="当天苹果|安卓下载次数" width="100" align="center">
<template slot-scope="scope">
{{ scope.row.today_hits_count.ios_today_hits_count }} | {{ scope.row.today_hits_count.android_today_hits_count }}
</template>
</el-table-column>
<el-table-column label="应用数量" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'app_info_list',params:{user_id:scope.row.id}}">
<el-link type="primary"> {{ scope.row.app_count }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="是否激活" width="80" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.is_active | statusFilter">{{ scope.row.is_active }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="私有存储权限" width="80" align="center">
<el-table-column class-name="status-col" label="私有存储" width="80" align="center">
<template slot-scope="scope">
<router-link v-if="scope.row.storage_active" :to="{name: 'storage_info_list',params:{user_id:scope.row.id}}">
<el-link type="primary"> <el-tag :type="scope.row.storage_active | statusFilter">{{ scope.row.storage_active }}</el-tag></el-link>
</router-link>
<el-tag v-else :type="scope.row.storage_active | statusFilter">{{ scope.row.storage_active }}</el-tag>
<el-tag :type="scope.row.storage_active | statusFilter">{{ scope.row.storage_active }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="超级签权限" width="80" align="center">
<el-table-column class-name="status-col" label="超级签" width="80" align="center">
<template slot-scope="scope">
<router-link v-if="scope.row.supersign_active" :to="{name: 'devices_info_list',params:{user_id:scope.row.id}}">
<el-link type="primary"> <el-tag :type="scope.row.supersign_active | statusFilter">{{ scope.row.supersign_active }}</el-tag></el-link>
</router-link>
<el-tag v-else :type="scope.row.supersign_active | statusFilter">{{ scope.row.supersign_active }}</el-tag>
<el-tag :type="scope.row.supersign_active | statusFilter">{{ scope.row.supersign_active }}</el-tag>
</template>
</el-table-column>
<el-table-column class-name="status-col" label="实名认证" width="95" align="center">
@ -109,7 +91,7 @@
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="110" class-name="small-padding fixed-width">
<el-table-column label="操作" align="center" width="100" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'user_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini">
@ -162,7 +144,7 @@ export default {
return statusMap[status]
},
certLableFilter(row) {
return baseFilter(row.certification, row.certification_status_choices)
return baseFilter(row.certification,row.certification_status_choices)
}
},
data() {

@ -1,157 +0,0 @@
<template>
<div class="app-container">
<el-form ref="postForm" :model="postForm" label-width="100px" :disabled="!is_edit">
<el-row>
<el-col :span="12">
<el-form-item label="用户ID">
<el-row :gutter="12">
<el-col :span="16">
<el-input :value="postForm.user_id" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="微信openid">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.openid" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="微信昵称">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.nickname" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="创建时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.created_time" disabled type="datetime" />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="订阅时间" prop="timestamp">
<el-row :gutter="20">
<el-col :span="8">
<el-date-picker :value="postForm.subscribe_time*1000" disabled type="datetime" />
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用户头像" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-image
:preview-src-list="[postForm.head_img_url]"
:src="postForm.head_img_url"
fit="contain"
style="width: 100px; height: 100px"
/>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="扫码登录" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.enable_login" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="消息通知" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-input v-model="postForm.enable_notify" disabled />
</el-col>
</el-row>
</el-form-item>
<el-form-item label="订阅状态" label-width="160px">
<el-row :gutter="12">
<el-col :span="16">
<el-select v-model="postForm.subscribe" class="filter-item" disabled>
<el-option v-for="item in wxbind_state_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-col :span="9" style="float: right">
<el-button v-if="!is_edit" type="primary" :disabled="postForm.id === postForm.used_id" @click="is_edit=true">修改</el-button>
<div v-else>
<el-button type="primary" @click="is_edit=false">取消</el-button>
<el-button type="primary" @click="updateData">保存修改</el-button>
</div>
</el-col>
</div>
</template>
<script>
import { getWxBindInfos, updateWxBindInfo } from '@/api/wxbind'
const wxbind_state_choices = [
{ id: false, name: '未订阅' },
{ id: true, name: '已订阅' }
]
const defaultForm = {
user_id: undefined,
address: undefined,
created_time: undefined,
head_img_url: undefined,
id: undefined,
nickname: undefined,
openid: undefined,
subscribe: undefined,
subscribe_time: undefined,
enable_login: undefined,
enable_notify: undefined
}
export default {
name: 'OrderDetail',
components: { }, filters: {},
data() {
return {
postForm: Object.assign({}, defaultForm),
loading: false,
is_edit: false,
wxbind_state_choices,
id: ''
}
},
computed: {},
created() {
this.id = this.$route.params && this.$route.params.id
this.fetchData(this.id)
},
methods: {
fetchData(id) {
getWxBindInfos(id).then(response => {
if (response.data) {
this.postForm = response.data
}
}).catch(err => {
console.log(err)
})
},
updateData() {
updateWxBindInfo(this.id, this.postForm).then(response => {
this.$message.success('更新成功')
this.postForm = response.data
}).catch(err => {
console.log(err)
})
}
}
}
</script>
<style scoped>
</style>

@ -1,192 +0,0 @@
<template>
<div class="app-container">
<div class="filter-container">
<el-input v-model="listQuery.user_id" placeholder="用户ID" style="width: 140px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.openid" placeholder="微信openid" style="width: 300px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-input v-model="listQuery.nickname" placeholder="微信昵称" style="width: 200px;" class="filter-item" clearable @keyup.enter.native="handleFilter" />
<el-select v-model="listQuery.subscribe" placeholder="玩家是否订阅" clearable class="filter-item" style="width: 140px" @change="handleFilter">
<el-option v-for="item in wxbind_state_choices" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-select v-model="listQuery.ordering" style="width: 140px" class="filter-item" @change="handleFilter">
<el-option v-for="item in sortOptions" :key="item.key" :label="item.label" :value="item.key" />
</el-select>
<el-button v-waves class="filter-item" type="primary" icon="el-icon-search" @click="handleFilter">
Search
</el-button>
</div>
<el-table
v-loading="listLoading"
:data="list"
element-loading-text="Loading"
border
fit
highlight-current-row
stripe
>
<el-table-column align="center" label="ID" width="90">
<template slot-scope="scope">
{{ scope.row.id }}
</template>
</el-table-column>
<el-table-column label="用户ID" width="100" align="center">
<template slot-scope="scope">
<router-link :to="{name: 'user_info_edit',params:{id:scope.row.user_id}}">
<el-link type="primary"> {{ scope.row.user_id }}</el-link>
</router-link>
</template>
</el-table-column>
<el-table-column label="头像" align="center">
<template slot-scope="scope">
<el-image :src="scope.row.head_img_url" :preview-src-list="[scope.row.head_img_url]" fit="contain" style="width: 80px; height: 80px" />
</template>
</el-table-column>
<el-table-column label="微信昵称" min-width="180px" align="center">
<template slot-scope="scope">
{{ scope.row.nickname }}
</template>
</el-table-column>
<el-table-column label="微信openid" min-width="280px" align="center">
<template slot-scope="scope">
{{ scope.row.openid }}
</template>
</el-table-column>
<el-table-column label="允许扫码登录" min-width="100px" align="center">
<template slot-scope="scope">
{{ scope.row.enable_login }}
</template>
</el-table-column>
<el-table-column label="允许消息通知" min-width="100px" align="center">
<template slot-scope="scope">
{{ scope.row.enable_notify }}
</template>
</el-table-column>
<el-table-column class-name="status-col" label="订阅状态" width="95" align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.subscribe | statusFilter">{{ scope.row| statusLableFilter }}</el-tag>
</template>
</el-table-column>
<el-table-column align="center" prop="created_time" label="创建时间" width="120">
<template slot-scope="scope">
<i class="el-icon-time" />
<el-tooltip :content="scope.row.created_time">
<span>{{ scope.row.created_time|formatTime }}</span>
</el-tooltip>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
<template slot-scope="scope">
<router-link :to="{name: 'wxbind_info_edit',params:{id:scope.row.id}}">
<el-button type="primary" size="mini">
查看编辑
</el-button>
</router-link>
<el-button type="danger" size="mini" @click="remove_wxbind(scope.row.id)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total>0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.limit" @pagination="fetchData" />
</div>
</template>
<script>
import { deleteWxBind, getWxBindList } from '@/api/wxbind'
import { baseFilter } from '@/utils'
import Pagination from '@/components/Pagination' // secondary package based on el-pagination
import waves from '@/directive/waves' // waves directive
const sortOptions = [
{ label: '创建时间 Ascending', key: 'created_time' },
{ label: '创建时间 Descending', key: '-created_time' }
]
const wxbind_state_choices = [
{ id: false, name: '未订阅' },
{ id: true, name: '已订阅' }
]
export default {
name: 'WxBindInfo',
components: { Pagination },
directives: { waves },
filters: {
formatTime(time) {
if (time) {
return time.split('T')[0]
}
},
wxbindStatusFilter(status) {
const statusMap = {
'1': 'success',
'0': 'gray',
'2': 'info' }
return statusMap[status]
},
wxbindLableFilter(row) {
if (row.wxbind_type === 2) {
return '应用' + row.app_info.name + '域名'
}
return baseFilter(row.wxbind_type, row.wxbind_type_choices)
},
statusLableFilter(row) {
return baseFilter(row.subscribe, wxbind_state_choices)
},
statusFilter(status) {
const statusMap = {
true: 'success',
false: 'danger'
}
return statusMap[status]
}
},
data() {
return {
list: null,
listLoading: true,
total: 0,
listQuery: {
page: 1,
limit: 10,
user_id: undefined,
ordering: '-created_time',
openid: undefined,
nickname: undefined,
subscribe: undefined
},
sortOptions,
wxbind_state_choices
}
},
created() {
this.fetchData()
}, mounted() {
if (this.$route.params.user_id) {
this.listQuery.user_id = this.$route.params.user_id
}
console.log(this.$router)
},
methods: {
remove_wxbind(wxbind_id) {
deleteWxBind(wxbind_id).then(response => {
this.list = response.data
this.total = response.total
this.listLoading = false
})
},
handleFilter() {
this.listQuery.page = 1
this.fetchData()
},
fetchData() {
this.listLoading = true
getWxBindList(this.listQuery).then(response => {
this.list = response.data.results
this.total = response.data.count
this.listLoading = false
})
}
}
}
</script>

@ -39,48 +39,5 @@ yarn lint
```
## deploy nginx
#### short deploy nginx
```shell
location / {
try_files $uri $uri/ /short.html;
}
```
#### index.html
```html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="3;URL=https://flyapps.cn">
<title>FLY分发平台</title>
<style>
.container {
width: 60%;
margin: 10% auto 0;
background-color: #f0f0f0;
padding: 2% 5%;
border-radius: 10px
}
ul {
padding-left: 20px;
}
ul li {
line-height: 2.3
}
a {
color: #20a53a
}
</style>
</head>
<body>
<div class="container">
<h3>这是分发平台下载默认页,正在跳转 <a href="https://flyapps.cn">首页</a></h3>
</div>
</body>
</html>
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

File diff suppressed because it is too large Load Diff

@ -5,13 +5,12 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"analyz": "vue-cli-service serve analyz",
"lint": "vue-cli-service lint"
},
"dependencies": {
"ali-oss": "^6.5.1",
"app-info-parser": "^0.3.9",
"axios": "^0.22.0",
"axios": "^0.19.0",
"core-js": "^3.4.4",
"element-ui": "^2.13.0",
"js-base64": "^2.5.2",
@ -20,7 +19,6 @@
"vue": "^2.6.10",
"vue-clipboard2": "^0.3.1",
"vue-cookies": "^1.7.0",
"vue-cropper": "^0.5.7",
"vue-lazyload": "^1.3.3",
"vue-qr": "^2.3.0",
"vue-router": "^3.1.3",
@ -32,14 +30,9 @@
"@vue/cli-service": "^4.1.0",
"babel-eslint": "^10.0.3",
"babel-plugin-component": "^1.1.1",
"compression-webpack-plugin": "5.0.1",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"html-webpack-plugin": "^5.3.1",
"script-ext-html-webpack-plugin": "^2.1.5",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-template-compiler": "^2.6.10",
"webpack-bundle-analyzer": "^4.4.1"
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,

@ -0,0 +1,16 @@
window.g = {
baseUrl: 'https://app.hehelucky.cn',
PaymentQuestionMsg:'如对充值订单有疑问,请联系 nineven@qq.com',
footer: {
copyright: 'Copyright © 2017-2020 第九系艾文 版权所有.',
ipcBeiAn: {
url: 'https://beian.miit.gov.cn',
text: '豫ICP备15004336号',
},
gongAnBeiAn: {
url: 'http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=41142202000049',
text: '豫公网安备 41142202000049号',
},
}
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

@ -2,9 +2,10 @@
<html lang="zh">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
<link href="<%= BASE_URL %>favicon.ico" rel="icon">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<script src="<%= BASE_URL %>config.js"></script>
<script src="<%= BASE_URL %>gt.js"></script>
<title>FLY分发平台</title>
</head>

@ -2,10 +2,10 @@
<html lang="zh">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
<link href="<%= BASE_URL %>favicon.ico" rel="icon">
<script src="<%= BASE_URL %>gt.js"></script>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<script src="<%= BASE_URL %>config.js"></script>
<title>应用下载</title>
</head>
<body>

@ -1,87 +1,87 @@
<template>
<div id="app" ref="app">
<router-view/>
</div>
<div id="app" ref="app">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {}
},
}
export default {
name: 'app',
data() {
return {}
},
}
</script>
<style>
body {
background-color: #F6F6F6;
}
html, body {
height: 100%;
}
@font-face {
font-family: "iconfont";
src: url('./assets/icon/iconfont.eot?t=1583637102320'); /* IE9 */
src: url('./assets/icon/iconfont.eot?t=1583637102320#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
url('./assets/icon/iconfont.woff?t=1583637102320') format('woff'),
url('./assets/icon/iconfont.ttf?t=1583637102320') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url('./assets/icon/iconfont.svg?t=1583637102320#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-android:before {
content: "\e61d";
}
.icon-android1:before {
content: "\e616";
}
.icon-android2:before {
content: "\e62a";
}
.icon-ios:before {
content: "\e60c";
}
.icon-IOS:before {
content: "\e68c";
}
.icon-ios1:before {
content: "\e601";
}
.icon-ios2:before {
content: "\e64b";
}
.icon-Android:before {
content: "\e6d4";
}
.icon-android3:before {
content: "\e625";
}
.canvas {
position: fixed;
left: -0px;
top: 58px;
height: 100%;
z-index: -2;
width: 100%;
}
body {
background-color: #F6F6F6;
}
html, body {
height: 100%;
}
@font-face {
font-family: "iconfont";
src: url('./assets/icon/iconfont.eot?t=1583637102320'); /* IE9 */
src: url('./assets/icon/iconfont.eot?t=1583637102320#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
url('./assets/icon/iconfont.woff?t=1583637102320') format('woff'),
url('./assets/icon/iconfont.ttf?t=1583637102320') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url('./assets/icon/iconfont.svg?t=1583637102320#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-android:before {
content: "\e61d";
}
.icon-android1:before {
content: "\e616";
}
.icon-android2:before {
content: "\e62a";
}
.icon-ios:before {
content: "\e60c";
}
.icon-IOS:before {
content: "\e68c";
}
.icon-ios1:before {
content: "\e601";
}
.icon-ios2:before {
content: "\e64b";
}
.icon-Android:before {
content: "\e6d4";
}
.icon-android3:before {
content: "\e625";
}
.canvas {
position: fixed;
left: -0px;
top: 58px;
height: 100%;
z-index: -2;
width: 100%;
}
</style>

@ -1,75 +1,75 @@
<template>
<div id="download" ref="download">
<router-view></router-view>
</div>
<div id="download" ref="download">
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'download',
data() {
return {}
},
}
export default {
name: 'download',
data() {
return {}
},
}
</script>
<style>
body {
background-color: #F6F6F6;
}
@font-face {
font-family: "iconfont";
src: url('./assets/icon/iconfont.eot?t=1583637102320'); /* IE9 */
src: url('./assets/icon/iconfont.eot?t=1583637102320#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
url('./assets/icon/iconfont.woff?t=1583637102320') format('woff'),
url('./assets/icon/iconfont.ttf?t=1583637102320') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url('./assets/icon/iconfont.svg?t=1583637102320#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-android:before {
content: "\e61d";
}
.icon-android1:before {
content: "\e616";
}
.icon-android2:before {
content: "\e62a";
}
.icon-ios:before {
content: "\e60c";
}
.icon-IOS:before {
content: "\e68c";
}
.icon-ios1:before {
content: "\e601";
}
.icon-ios2:before {
content: "\e64b";
}
.icon-Android:before {
content: "\e6d4";
}
.icon-android3:before {
content: "\e625";
}
body {
background-color: rgba(0, 166, 224, 0.18)
}
@font-face {
font-family: "iconfont";
src: url('./assets/icon/iconfont.eot?t=1583637102320'); /* IE9 */
src: url('./assets/icon/iconfont.eot?t=1583637102320#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
url('./assets/icon/iconfont.woff?t=1583637102320') format('woff'),
url('./assets/icon/iconfont.ttf?t=1583637102320') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url('./assets/icon/iconfont.svg?t=1583637102320#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-android:before {
content: "\e61d";
}
.icon-android1:before {
content: "\e616";
}
.icon-android2:before {
content: "\e62a";
}
.icon-ios:before {
content: "\e60c";
}
.icon-IOS:before {
content: "\e68c";
}
.icon-ios1:before {
content: "\e601";
}
.icon-ios2:before {
content: "\e64b";
}
.icon-Android:before {
content: "\e6d4";
}
.icon-android3:before {
content: "\e625";
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

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

Loading…
Cancel
Save