themblem/web/src/views/estor-batches.vue

290 lines
8.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<h3>
数据管理
</h3>
<hr>
<div class="fade-in">
<div class="my-2">
<button @click="reload" class="btn btn-secondary">
<font-awesome-icon icon="refresh" class="me-1"/>刷新
</button>
<button class="btn btn-success ms-2" @click="$router.push('/estor/import')">从USB导入</button>
<button class="btn btn-info ms-2" @click="$router.push('/estor/config')">配置</button>
</div>
<div class="input-group my-4">
<input class="form-control" type="text" v-model="search_query" />
<button class="btn btn-secondary" @click="search">搜索</button>
</div>
<div v-if="loading" class="alert alert-info">
正在加载...
<CSpinner class="d-block my-3"/>
</div>
<div v-else-if="!entries.length" class="alert alert-info">
没有数据
</div>
<table v-else class="table">
<thead>
<tr>
<th>
批次名称
</th>
<th>导入时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="e, i in entries" :key="i">
<td>
<a href="/estor/files?">{{ e.name }}</a>
</td>
<td>{{ show_time(e) }}</td>
<td>{{ show_status(e) }}</td>
<td>
<CDropdown>
<CDropdownToggle size="sm" color="secondary">操作</CDropdownToggle>
<CDropdownMenu>
<CDropdownItem href="javascript:void(0)" @click="qr_std_upload(e)">
备案到云
</CDropdownItem>
<CDropdownItem href="javascript:void(0)" @click="show_image_convert(e)">
图像格式转换
</CDropdownItem>
<CDropdownItem class="text-danger" href="javascript:void(0)" @click="delete_batch(e)">
删除
</CDropdownItem>
</CDropdownMenu>
</CDropdown>
</td>
</tr>
</tbody>
</table>
<Modal title="图片转换" ref="image_convert_modal" @close="image_convert">
<div class="mb-3">
数据批次
<strong>
{{ image_convert_target.name }}
</strong>
</div>
<div>
任务配置
</div>
<div class="convert-config-section">
转换文件格式
<div>
<select v-model="image_convert_format" class="form-control">
<option value="jpeg">JPEG</option>
<option value="png">PNG</option>
</select>
</div>
</div>
<div class="convert-config-section">
分辨率
<div>
长边限制像素
<input class="d-block form-control" type="text" v-model="image_convert_size"/>
</div>
</div>
<div class="convert-config-section">
输出图像质量
<select v-model="image_convert_quality" class="d-block form-control">
<option value="high">高</option>
<option value="middle">中</option>
<option value="low">低</option>
</select>
<div>
<input type="checkbox" id="icg" v-model="image_convert_grayscale" />
<label for="icg">灰度化</label>
</div>
</div>
</Modal>
</div>
</template>
<script>
export default {
name: 'EstorBatches',
props: [],
components: {
},
data: function () {
return {
loading: true,
search_query: '',
entries: [],
image_convert_target: null,
image_convert_format: "jpeg",
image_convert_size_enabled: false,
image_convert_size: 1920,
image_convert_quality: 'high',
image_convert_grayscale: false,
};
},
computed: {
},
methods: {
search() {
this.$router.push("/estor/search?q=" + this.search_query);
},
get_batch_dir(e, n) {
return this.batch_dir(e.name, n);
},
delete_batch(e) {
this.sms_verified_action(
'删除批次',
'确定删除批次吗?名称: ' + e.name,
() => this.do_delete_batch(e))
},
async do_delete_batch(e) {
await this.$root.estor_delete(this.get_batch_dir(e));
this.reload();
},
show_image_convert(e) {
this.image_convert_target = e;
this.$refs.image_convert_modal.show();
},
async image_convert(ok) {
if (!ok) return;
this.$root.notify("格式转换", "正在创建格式转换任务");
var e = this.image_convert_target;
var ib = this.get_batch_dir(e, "import");
var params = {
'Estor-Addr': this.estor_url,
'Estor-Token': this.estor_token,
'Input-Basedir': ib,
'Output-Basedir': this.get_batch_dir(e, "convert/"),
'Output-Format': this.image_convert_format,
'Resize': this.image_convert_size.toString(),
'Quality': this.image_convert_quality,
};
if (this.image_convert_grayscale) {
params['Grayscale'] = 'yes';
}
var r = await this.$root.estor_call("CreateJob", {
name: "[格式转换]" + e.name,
spec: {
type: "HttpPost",
args: {
space: this.$root.estor_space,
files: [this.get_batch_dir(e, "import")],
url: this.emblem_processor_url + '/image-convert',
params,
recursive: true,
},
},
});
console.log(r);
this.$router.push("/estor/jobs");
},
async qr_std_upload(e) {
this.$root.notify("备案到云", "正在创建备案任务");
var base_url = this.base_url();
var url = base_url + '/api/v1/qr-std-upload/';
var r = await this.$root.estor_call("CreateJob", {
name: "[备案到云]" + e.name,
spec: {
type: "HttpPost",
args: {
space: this.$root.estor_space,
files: [this.get_batch_dir(e, 'import')],
url,
concurrency: 256,
recursive: true,
headers: {
'Authorization': 'token ' + this.$root.token,
},
},
},
});
await this.set_batch_state(e.name, {
status: 'qr_std_uploading',
job: r.data.data,
});
this.$router.push("/estor/jobs");
},
show_time(e) {
var ctime = e.ts.ctime;
return this.format_timestamp(ctime, false);
},
show_status(e) {
var m = {
loading: '正在获取...',
importing: '正在导入',
imported: '已导入',
qr_std_uploading: '备案中',
qr_std_uploaded: '备案完成',
feature_extracting: '特征提取中',
feature_extracted: '特征提取完成',
error: '获取失败',
};
return m[e.status] || e.status;
},
async reload() {
this.loading = true;
var r = await this.$root.estor_list(this.$root.batches_dir);
var entries = this.sort_entries(r);
for (var i in entries) {
entries[i].status ='loading';
}
this.entries = entries;
this.loading = false;
this.reload_status();
},
async reload_status() {
for (var i in this.entries) {
var e = this.entries[i];
try {
var r = await this.get_batch_state(e.name);
console.log(r);
var st = r['status'];
if (!r.job) {
this.entries[i].status = st;
continue;
}
if (st == 'importing') {
st = await this.patch_status_if_completed(e.name, r.job, st, 'imported');
} else if (st == 'qr_std_uploading') {
st = await this.patch_status_if_completed(e.name, r.job, st, 'qr_std_uploaded');
}
this.entries[i].status = st;
} catch {
this.entries[i].status = 'error';
}
}
},
async patch_status_if_completed(batch_name, job_id, old_st, new_st) {
var r = await this.$root.estor_call("GetJob", {
uuid: job_id,
});
if (r.data.data.status == 'Completed') {
this.set_batch_state(batch_name, {
status: new_st,
});
return new_st;
}
return old_st;
},
sort_entries(r) {
var sorted = r.sort((a, b) => { return b.ts.ctime - a.ts.ctime });
return sorted;
},
},
mounted() {
this.reload();
},
}
</script>
<style scoped>
div.convert-config-section {
border: 1px solid #ccc;
border-radius: 5px;
padding: 0.5rem;
margin: 0.5rem 0;
}
input[type=checkbox] {
margin-right: 0.2rem;
}
</style>