同步脚步

This commit is contained in:
邓皓元 2019-01-17 17:08:34 +08:00
parent 983ced61e1
commit e5d04e6441
31 changed files with 750 additions and 110 deletions

View File

View File

@ -0,0 +1,33 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateArtisansTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('artisans', function (Blueprint $table) {
$table->increments('id');
$table->string('command', 32)->default('')->comment('命令类型');
$table->text('parameters')->nullable()->comment('参数');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('artisans');
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Domains\Artisan\Http\Controllers;
use App\Core\Controller;
use Illuminate\Http\Request;
use App\Models\Artisan\Artisan;
use App\Domains\Artisan\Services\ArtisanService;
class ArtisanController extends Controller
{
protected $request;
protected $artisanService;
/**
* 构造函数,自动注入.
*/
public function __construct(Request $request, ArtisanService $artisanService)
{
$this->request = $request;
$this->artisanService = $artisanService;
}
/**
* 列表.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$conditions = $this->request->all();
$res = $this->artisanService->index($conditions);
return res($res, '命令记录', 201);
}
/**
* 执行.
*
* @return \Illuminate\Http\Response
*/
public function call()
{
set_time_limit(0);
ini_set('memory_limit', '4096m');
ini_set('default_socket_timeout', -1);
$command = $this->request->get('command');
$parameters = $this->request->get('parameters', []);
$this->artisanService->call($command, $parameters);
return res(true, '执行成功');
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Domains\Artisan\Providers;
use Illuminate\Support\ServiceProvider;
use App\Domains\Artisan\Providers\RouteServiceProvider;
use Illuminate\Database\Eloquent\Factory as EloquentFactory;
class ArtisanServiceProvider extends ServiceProvider
{
/**
* 引导启动任何应用程序服务
*
* php artisan make:migration --path=app/Domains/Artisan/Database/migrations
*
* @return void
*/
public function boot()
{
$this->loadMigrationsFrom([realpath(__DIR__ . '/../Database/migrations')]);
// $this->app->make(EloquentFactory::class)->load(realpath(__DIR__ . '/../Database/factories'));
// $this->mergeConfigFrom(realpath(__DIR__ . '/../config.php'), 'domain.artisan');
}
/**
* 注册一个服务提供者
*
* @return void
*/
public function register()
{
$this->app->register(RouteServiceProvider::class);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Domains\Artisan\Providers;
use Dipper\Foundation\Core\RouteServiceProvider as ServiceProvider;
class RouteServiceProvider extends ServiceProvider
{
/**
* Read the routes from the "api.php" and "web.php" files of this Domain
*/
public function boot()
{
$app = $this->app;
$namespace = 'App\Domains\Artisan\Http\Controllers';
$pathApi = __DIR__.'/../Routes/api.php';
$pathWeb = __DIR__.'/../Routes/web.php';
$this->loadRoutesFiles($app->router, $namespace, $pathApi, $pathWeb);
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Domains\Artisan\Repositories;
use App\Core\Repository;
use App\Models\Artisan\Artisan as Model;
class ArtisanRepository extends Repository
{
/**
* 是否关闭缓存
*
* @var boolean
*/
protected $cacheSkip = false;
/**
* 是否开启数据转化
*
* @var bool
*/
protected $needTransform = false;
/**
* @var array
*/
protected $fieldSearchable = [
'id' => '=',
'created_at' => 'like',
];
public function model()
{
return Model::class;
}
/**
* 数据格式化
*
* @param mixed $result
*
* @return mixed
*/
public function transform($model)
{
return $model->toArray();
}
/**
* 查询条件
*
* @return void
*/
public function withConditions(array $conditions = [])
{
if (isset($conditions['id'])) {
$conditions['id'] = array_wrap($conditions['id']);
$this->model = $this->model->whereIn('id', $conditions['id']);
}
if (isset($conditions['command'])) {
$conditions['command'] = array_wrap($conditions['command']);
$this->model = $this->model->whereIn('command', $conditions['command']);
}
if (isset($conditions['starttime'])) {
$query->where('created_at', '>=', Carbon::parse($conditions['starttime']));
}
if (isset($conditions['endtime'])) {
$query->where('created_at', '<=', Carbon::parse($conditions['endtime']));
}
return $this;
}
}

View File

@ -0,0 +1,9 @@
<?php
// Prefix: /api/artisan
$router->group(['prefix' => 'artisan', 'as' => 'artisan', 'middleware' => ['adminAuth']], function($router) {
// The controllers live in Domains/Artisan/Http/Controllers
$router->get('/', ['as' => 'index', 'uses' => 'ArtisanController@index']);
$router->post('call', ['as' => 'call', 'uses' => 'ArtisanController@call']);
});

View File

@ -0,0 +1,14 @@
<?php
$router->group(['prefix' => 'artisans', 'as' => 'artisans'], function($router) {
// The controllers live in Domains/Artisan/Http/Controllers
// $router->get('/', ['as' => 'index', 'uses' => 'ArtisanController@index']);
/**
* 需要认证的接口
*/
// $router->group(['middleware' => ['userAuth']], function($router) {
// // $router->post('delete', ['as' => 'delete', 'uses' => 'ArtisanController@delete']);
// });
});

View File

@ -0,0 +1,64 @@
<?php
namespace App\Domains\Artisan\Services;
use App\Core\Service;
use App\Exceptions\NotAllowedException;
use Illuminate\Support\Facades\Artisan;
use Dipper\Foundation\Exceptions\HttpException;
use App\Domains\Artisan\Repositories\ArtisanRepository;
class ArtisanService extends Service
{
protected $artisanRepository;
/**
* 构造函数
*
* @return void
*/
public function __construct(ArtisanRepository $artisanRepository)
{
$this->artisanRepository = $artisanRepository;
}
/**
* 命令执行记录
*
* @return void
*/
public function index(array $conditions = [])
{
$limit = $conditions['limit'] ?? 20;
$artisans = $this->artisanRepository->withConditions($conditions)->applyConditions()->paginate($limit);
return $artisans;
}
/**
* 执行.
*
* @return \Illuminate\Http\Response
*/
public function call($command, array $parameters = [])
{
$commands = array_keys(Artisan::all());
if (!in_array($command, $commands)) {
throw new NotAllowedException('非法的命令');
}
try {
Artisan::call($command, $parameters);
$this->artisanRepository->create([
'command' => $command,
'parameters' => $parameters,
]);
} catch (\Exception $e) {
throw new HttpException($e->getMessage());
}
return res(true, '执行成功');
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Domains\Artisan\Tests\Services;
use App\Core\TestCase;
use App\Domains\Artisan\Services\ArtisanService;
class ArtisanServiceTest extends TestCase
{
public function testArtisanServiceTest()
{
$this->assertTrue(true);
}
}

View File

@ -0,0 +1,11 @@
{
"name": "app/artisan",
"description": "",
"type": "app-domain",
"require": {
},
"autoload": {
}
}

View File

@ -135,7 +135,7 @@ class OrderController extends Controller
public function cancel()
{
$ids = $this->request->ids();
foreach ($ids as $id) {
$res = $this->orderService->cancel($id);
}
@ -149,7 +149,7 @@ class OrderController extends Controller
public function received()
{
$ids = $this->request->ids();
foreach ($ids as $id) {
$res = $this->orderService->received($id);
}

View File

@ -50,7 +50,7 @@ class AddedOrderSync extends Command
$this->line('插入订单数据,条数:'.count($orders));
foreach (array_chunk($orders, $this->chunks) as $data) {
echo '.';
$this->getOutput()->write('.');
AddedOrder::upsert($orders, 'id');
}
app(AddedOrderRepository::class)->forgetCached();
@ -66,7 +66,7 @@ class AddedOrderSync extends Command
];
foreach ($dataOrderCards as $type => $orderCards) {
foreach (array_chunk($orderCards, $this->chunks) as $data) {
echo '.';
$this->getOutput()->write('.');
DB::table($tables[$type])->upsert($data, ['sim', 'order_id']);
}
}

View File

@ -38,7 +38,7 @@ class OrderBaseSync extends Command
try {
$this->line('插入订单数据,条数:'.count($orders));
foreach (array_chunk($orders, $this->chunks) as $data) {
echo '.';
$this->getOutput()->write('.');
foreach ($data as &$item) {
unset($item['package_id']);
}
@ -49,8 +49,8 @@ class OrderBaseSync extends Command
$this->line('插入订单关联数据,条数:'.count($cards));
foreach (array_chunk($cards, $this->chunks) as $data) {
echo '.';
OrderCard::upsert($data, ['sim', 'order_id']);
$this->getOutput()->write('.');
OrderCard::upsert($data, ['sim', 'deleted_at']);
}
app(OrderCardRepository::class)->forgetCached();
$this->line('插入订单关联数据成功');

View File

@ -1,47 +0,0 @@
<?php
namespace App\Domains\Virtual\Http\Controllers;
use App\Core\Controller;
use Illuminate\Http\Request;
use App\Exceptions\NotAllowedException;
use Illuminate\Support\Facades\Artisan;
use Dipper\Foundation\Exceptions\HttpException;
class ArtisanController extends Controller
{
protected $request;
/**
* 构造函数,自动注入.
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* 列表.
*
* @return \Illuminate\Http\Response
*/
public function call()
{
$command = $this->request->get('command');
$commands = array_keys(Artisan::all());
if (!in_array($command, $commands)) {
throw new NotAllowedException('非法的命令');
}
$parameters = $this->request->get('parameters', []);
try{
Artisan::call($command, $parameters);
}catch(\Exception $e){
throw new HttpException($e->getMessage());
}
return res(true, '执行成功');
}
}

View File

@ -5,7 +5,6 @@ $router->group(['prefix' => 'virtual', 'as' => 'virtual', 'middleware' => ['admi
// The controllers live in Domains/Virtual/Http/Controllers
$router->get('/', ['as' => 'index', 'uses' => 'VirtualController@index']);
$router->post('/artisan/call', ['as' => 'artisan.call', 'uses' => 'ArtisanController@call']);
// 名称查找
$router->get('/fetch/companies', ['as' => 'fetch.companies', 'uses' => 'FetchController@companies']);

View File

@ -0,0 +1,40 @@
<?php
namespace App\Models\Artisan;
use App\Core\Model;
class Artisan extends Model
{
protected $table = 'artisans';
protected $casts = [
'parameters' => 'array',
];
protected $appends = ['command_name'];
public static $names = [
'real:sync-added-order' => '同步RD企业订单数据',
'real:sync-bloc' => '同步RD集团数据',
'real:sync-company' => '同步RD企业数据',
'real:sync-mongo' => '同步卡基础信息数据',
'real:sync-order' => '同步RD基础订单数据',
'real:sync-package' => '同步RD套餐数据',
'virtual:sync-card' => '同步VD卡信息数据',
'virtual:sync-company' => '同步VD企业数据',
'virtual:sync-log' => '同步VD订单数据',
'virtual:sync-package' => '同步VD套餐数据',
'virtual:sync-product' => '同步VD定价',
];
/**
* 获取命令名称
*
* @return string
*/
public function getCommandNameAttribute()
{
return self::$names[$this->attributes['command']] ?? '';
}
}

View File

@ -1,6 +1,6 @@
[program:vd-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /www/vd/artisan queue:work --queue=default,sync --sleep=3 --tries=1 --memory=4096
command=php /www/vd/artisan queue:work --queue=default,sync --memory=4096
autostart=true
autorestart=true
user=www

View File

@ -26,7 +26,7 @@ const routes = [
{ path: '/exports', name: 'StatsExports', component: load('exports/index'), meta: { title: '导出记录' } },
{ path: '/stats/company-count', name: 'StatsCompanyCount', component: load('stats/company-count/index'), meta: { title: '企业统计' } },
{ path: '/stats/order/:type', name: 'StatsOrder', component: load('stats/order/index'), meta: { title: '订单统计' } },
{ path: '/stats/company-report/:type', name: 'StatsCompanyReport', component: load('stats/company-report/index'), meta: { title: '月报表' } }
{ path: '/artisan/real-sync', name: 'RealSync', component: load('artisan/real-sync/index'), meta: { title: 'RD数据同步' } }
]
},
{ path: '*', redirect: { path: '/home' } }

View File

@ -0,0 +1,64 @@
<template>
<Modal
:closable="false"
:mask-closable="false"
:title="'RD数据同步'"
@on-visible-change="visibleChange"
v-model="my_show"
:width="1200"
>
<div class="page-edit-wrap uinn-lr20">
<ui-loading :show="page_loading.show"></ui-loading>
<Steps :current="current" :status="circle.status">
<Step
:title="item.title"
:content="item.content"
v-for="(item, index) in steps"
:key="index"
></Step>
</Steps>
<Row type="flex" justify="center" class="umar-t15" v-if="[3, 4].indexOf(current) !== -1">
<DatePicker
:editable="false"
placeholder="请选择时间"
placement="bottom-start"
type="month"
v-model.trim="month"
></DatePicker>
</Row>
<Row type="flex" justify="center" class="umar-t15">
<Circle :size="250" :percent="circle.percent" stroke-linecap="square">
<div class="circle-text">
<h1>{{circle.percent}}%</h1>
<br>
<p>{{circle.content}}</p>
</div>
</Circle>
</Row>
</div>
<footer class="ta-c" slot="footer">
<Button @click="clear" class="w-80" ghost type="primary" :disabled="disabled">取消</Button>
<Button
:loading="loading"
@click="call"
class="w-80"
type="primary"
v-if="this.circle.status !== 'finish'"
:disabled="disabled"
>下一步</Button>
<Button
:loading="loading"
@click="clear"
class="w-80"
type="primary"
v-if="this.circle.status === 'finish'"
>完成</Button>
</footer>
</Modal>
</template>
<script src="./js/edit.js"></script>

View File

@ -0,0 +1,82 @@
<template>
<div class="page-wrap">
<ui-loading :show="page_loading.show"></ui-loading>
<div class="page-handle-wrap">
<ul class="handle-wraper bd-b">
<li class="f-l">
<div class="text-exp">
<b>全部信息</b>
</div>
</li>
<li class="f-r">
<div class="handle-item">
<Button @click="openEdit(true)" icon="md-add" type="primary">执行同步</Button>
</div>
<div class="handle-item">
<Button @click="search.show=!search.show" ghost icon="ios-search" type="primary">搜索</Button>
</div>
<div class="handle-item">
<Button @click="index(1)" icon="md-refresh">刷新</Button>
</div>
</li>
</ul>
<div class="search-wrap" v-show="search.show">
<ul class="handle-wraper">
<li class="handle-item w-250">
<Select clearable placeholder="命令类型" v-model="options.command">
<Option :value="index" v-for="(name, index) in commands" :key="index">{{name}}</Option>
</Select>
</li>
<li class="handle-item w-250">
<DatePicker
:editable="false"
placeholder="请选择时间"
placement="bottom-start"
type="daterange"
v-model.trim="options.time"
></DatePicker>
</li>
<li class="f-r">
<div class="handle-item">
<Button @click="index(1)" ghost type="primary">立即搜索</Button>
</div>
<div class="handle-item">
<Button @click="resetSearch" ghost type="warning">重置搜索</Button>
</div>
</li>
</ul>
</div>
</div>
<div class="page-list-wrap">
<Table :columns="table_titles" :data="list_data ? list_data.data : []"></Table>
</div>
<div class="page-turn-wrap" v-if="list_data">
<Page
:current="Number(list_data.current_page)"
:page-size="Number(list_data.per_page)"
:total="Number(list_data.total)"
@on-change="index"
show-elevator
show-total
></Page>
</div>
<ui-edit
:data="editObj.data"
:isUpdate.sync="editObj.isUpdate"
:show.sync="editObj.show"
@add-success="index"
@update-success="index(list_data.current_page)"
></ui-edit>
</div>
</template>
<script src="./js/index.js"></script>

View File

@ -0,0 +1,104 @@
export default {
props: {
show: {
type: Boolean,
default: false
}
},
watch: {
show(bool) {
this.my_show = bool;
if (bool) {
this.current = 0;
this.circle.status = 'wait';
this.circle.percent = 0;
this.circle.content = '未开始';
}
}
},
data() {
return {
my_show: false,
loading: false,
disabled: false,
steps: [
{ 'title': '同步集团', 'content': '所有卡源集团的数据', 'command': 'real:sync-bloc', 'max': 5 },
{ 'title': '同步企业', 'content': '所有下级企业的数据', 'command': 'real:sync-company', 'max': 10 },
{ 'title': '同步套餐', 'content': '所有套餐的数据', 'command': 'real:sync-package', 'max': 30 },
{ 'title': '同步订单', 'content': '指定月份的销售订单数据', 'command': 'real:sync-order', 'max': 65 },
{ 'title': '同步企业订单', 'content': '指定月份的续费及增值包数据', 'command': 'real:sync-added-order', 'max': 100 }
],
current: 0,
circle: {
status: 'wait',
percent: 0,
content: '未开始'
},
month: this.moment().subtract('2', 'months').startOf('month').format('YYYY-MM')
};
},
methods: {
call() {
this.disabled = true;
let params = {};
params.command = this.steps[this.current]['command'];
if (!params.command) {
return this.$Message.error('命令错误');
}
if ([3, 4].indexOf(this.current) !== -1) {
if (!this.month) {
return this.$Message.error('请选择要同步的月份');
}
params.parameters = { month: this.moment(this.month).format('YYYY-MM') };
}
let max = this.steps[this.current]['max'];
this.circle.status = 'process';
this.circle.content = '正在' + this.steps[this.current]['title'];
let interval = setInterval(() => {
if (this.circle.percent < max) {
this.circle.percent++;
}
}, 1000);
service.post('/api/artisan/call', params).then(res => {
if (res.code == 0) {
this.$Message.success('同步成功');
this.current++;
this.circle.percent = max;
this.circle.content = this.steps[this.current]['title'] + '完成';
this.circle.status = this.circle.percent === 100 ? 'finish' : 'wait';
} else {
this.circle.percent = this.steps[this.current - 1]['max'];
this.circle.content = '同步失败';
this.circle.status = 'error';
}
this.disabled = false;
clearInterval(interval);
}).catch(() => {
this.circle.percent = this.steps[this.current - 1]['max'];
this.circle.content = '同步失败';
this.circle.status = 'error';
this.disabled = false;
clearInterval(interval);
});
},
visibleChange(bool) {
if (!bool) {
this.$emit('update:show', false);
}
},
clear() {
this.my_show = false;
}
}
};

View File

@ -0,0 +1,112 @@
export default {
name: 'RealSync',
components: {
UiEdit: resolve => require(['views/artisan/real-sync/edit'], resolve)
},
data() {
return {
commands: {
'real:sync-added-order': '同步RD企业订单数据',
'real:sync-bloc': '同步RD集团数据',
'real:sync-company': '同步RD企业数据',
'real:sync-mongo': '同步卡基础信息数据',
'real:sync-order': '同步RD基础订单数据',
'real:sync-package': '同步RD套餐数据'
},
options: {
command: null,
time: []
},
list_data: null,
editObj: {
show: false,
data: null
},
search: {
show: false
},
table_titles: [
{
title: 'ID',
key: 'id',
width: 80
},
{
title: '名称',
key: 'command_name',
width: 300
},
{
title: '命令',
key: 'command'
},
{
title: '参数',
key: 'parameters'
},
{
title: '执行时间',
key: 'created_at',
width: 170
}
]
};
},
created() {
this.index(1);
},
methods: {
/**
* [index 列表]
* @param {Number} page [description]
* @return {[type]} [description]
*/
index(page = 1) {
let params = this.searchDataHandle({}, { page }, this.options);
params.command = params.command ? params.command : Object.keys(this.commands);
this.isShowLoading(true);
service.get('/api/artisan', { params }).then(res => {
this.isShowLoading(false);
if (res.code == 0) {
this.list_data = res.data;
}
}).catch(() => {
this.isShowLoading(false);
});
},
/**
* [openEdit 打开编辑弹窗]
* @return {[type]} [description]
*/
openEdit(bool) {
this.editObj = {
show: bool
};
},
/**
* [request 刷新]
* @return {[type]} [description]
*/
request() {
const result = this.list_data;
let page = result.current_page;
if (this.list_data.data.length == 1) {
page = this.returnPage(result.total, result.current_page, result.per_page);
}
this.index(page);
},
resetSearch() {
this.options.command = null;
this.options.time = [];
this.index(1);
}
}
};

View File

@ -7,7 +7,7 @@
<AutoComplete @on-search="handleSearchCompanies" placeholder="输入名称进行过滤"></AutoComplete>
</div>
<div class="box">
<CellGroup @on-click="index" v-for="item in companies">
<CellGroup @on-click="index" v-for="{item, index} in companies" :key="index">
<Cell :name="item.id" :selected="item.id == params.company_id ? true : false" :title="item.name"/>
</CellGroup>
</div>

View File

@ -1,51 +0,0 @@
<template>
<div class="page-wrap">
<ui-loading :show="page_loading.show"></ui-loading>
<div class="page-handle-wrap">
<ul class="handle-wraper bd-b">
<li class="f-l">
<div class="text-exp">
<b>全部信息</b>
</div>
</li>
<li class="f-r">
<div class="handle-item">
<Button @click="openEdit(true, null)" icon="md-add" type="primary" v-has="'create'">添加企业</Button>
</div>
</li>
</ul>
<div class="search-wrap" v-show="search.show">
<ul class="handle-wraper">
<li class="handle-item w-250">
<Input clearable placeholder="订单编号" v-model.trim="params.sn"></Input>
</li>
<li class="handle-item w-250">
<AutoComplete @on-search="handleCompleteCompanies" icon="ios-search" placeholder="企业名称" v-model.trim="params.company_name">
<Option :key="item.id" :value="item.name" v-for="item in completeHandledCompanies">{{ item.name }}</Option>
</AutoComplete>
</li>
<li class="handle-item w-250">
<AutoComplete @on-search="handleCompletePackages" icon="ios-search" placeholder="套餐名称" v-model.trim="params.package_name">
<Option :key="item.id" :value="item.name" v-for="item in completeHandledPackages">{{ item.name }}</Option>
</AutoComplete>
</li>
<li class="handle-item w-250">
<DatePicker :editable="false" placeholder="请选择时间" placement="bottom-start" type="daterange" v-model.trim="params.time"></DatePicker>
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
}
</script>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=\favicon.ico><script src=\config.js></script><title></title><link href=/css/chunk-16e58b91.b353955b.css rel=prefetch><link href=/js/chunk-00ae0766.9e6b7bf3.js rel=prefetch><link href=/js/chunk-16e58b91.a8e227cd.js rel=prefetch><link href=/css/app.36043160.css rel=preload as=style><link href=/css/chunk-vendors.3c3b2e85.css rel=preload as=style><link href=/js/app.5714659c.js rel=preload as=script><link href=/js/chunk-vendors.02a4e5bc.js rel=preload as=script><link href=/css/chunk-vendors.3c3b2e85.css rel=stylesheet><link href=/css/app.36043160.css rel=stylesheet></head><body><noscript><strong>很抱歉如果没有启用JavaScript程序不能正常工作若要继续使用请启用它。</strong></noscript><div id=app></div><script src=/js/chunk-vendors.02a4e5bc.js></script><script src=/js/app.5714659c.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=\favicon.ico><script src=\config.js></script><title></title><link href=/css/chunk-a9352a12.58297237.css rel=prefetch><link href=/js/chunk-00ae0766.9e6b7bf3.js rel=prefetch><link href=/js/chunk-a9352a12.f3e063ef.js rel=prefetch><link href=/css/app.36043160.css rel=preload as=style><link href=/css/chunk-vendors.3c3b2e85.css rel=preload as=style><link href=/js/app.61083e03.js rel=preload as=script><link href=/js/chunk-vendors.02a4e5bc.js rel=preload as=script><link href=/css/chunk-vendors.3c3b2e85.css rel=stylesheet><link href=/css/app.36043160.css rel=stylesheet></head><body><noscript><strong>很抱歉如果没有启用JavaScript程序不能正常工作若要继续使用请启用它。</strong></noscript><div id=app></div><script src=/js/chunk-vendors.02a4e5bc.js></script><script src=/js/app.61083e03.js></script></body></html>