feat: init
This commit is contained in:
192
frontend/src/components/Devices.vue
Normal file
192
frontend/src/components/Devices.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<script setup lang="ts">
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
interface Device {
|
||||
ID: number
|
||||
UUID: string
|
||||
Name: string
|
||||
Expert: string
|
||||
ExpertName: string
|
||||
State: string
|
||||
Note: string
|
||||
}
|
||||
|
||||
interface Expert {
|
||||
ID: number
|
||||
UID: string
|
||||
SecUID: string
|
||||
ShortID: string
|
||||
RealName: string
|
||||
NickName: string
|
||||
State: string
|
||||
Since: number
|
||||
Focus: number
|
||||
Total: number
|
||||
Voice: string
|
||||
Hello: string
|
||||
}
|
||||
|
||||
|
||||
const devices = ref<Device[]>([]);
|
||||
|
||||
const experts = ref<Expert[]>([]);
|
||||
|
||||
// use axios get /experts onMount
|
||||
const loadExperts = function () {
|
||||
axios.get<Expert[]>('/api/experts').then((resp: AxiosResponse) => {
|
||||
experts.value = resp.data;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// use axios get /experts onMount
|
||||
const loadDevices = function () {
|
||||
axios.get<Device[]>('/api/devices').then((resp: AxiosResponse) => {
|
||||
devices.value = resp.data;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDevices()
|
||||
});
|
||||
|
||||
setInterval(loadDevices, 30 * 1000);
|
||||
|
||||
const currentDevice = ref<Device | null>(null);
|
||||
const showDeviceUpdateModal = (id: number) => {
|
||||
loadExperts();
|
||||
|
||||
currentDevice.value = devices.value.find((device) => device.ID === id) || null;
|
||||
|
||||
selectExpert.value = currentDevice.value?.Expert
|
||||
deviceName.value = currentDevice.value?.Name
|
||||
|
||||
const dialog = document.getElementById("show_device_update_modal") as HTMLDialogElement;
|
||||
dialog.showModal();
|
||||
};
|
||||
|
||||
const resetStateNormal = (id: number) => {
|
||||
currentDevice.value = devices.value.find((device) => device.ID === id) || null;
|
||||
if (!currentDevice.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// alert to confirm
|
||||
if (!confirm(`确定要恢复 ${currentDevice.value.UUID} 的自动关注吗?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = { state: "" };
|
||||
axios.patch(`/api/devices/${currentDevice.value?.UUID}/state`, data).then((resp: AxiosResponse) => {
|
||||
console.log(resp.data)
|
||||
loadDevices();
|
||||
});
|
||||
};
|
||||
|
||||
const setStateStop = (id: number) => {
|
||||
currentDevice.value = devices.value.find((device) => device.ID === id) || null;
|
||||
if (!currentDevice.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// alert to confirm
|
||||
if (!confirm(`确定要停止 ${currentDevice.value.UUID} 的自动关注吗?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = { state: "stop" };
|
||||
axios.patch(`/api/devices/${currentDevice.value?.UUID}/state`, data).then((resp: AxiosResponse) => {
|
||||
console.log(resp.data)
|
||||
loadDevices();
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = function () {
|
||||
const dialog = document.getElementById("show_device_update_modal") as HTMLDialogElement;
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
const deviceName = ref<String | null>();
|
||||
const selectExpert = ref<String | null>();
|
||||
const saveDate = () => {
|
||||
const data = {
|
||||
name: deviceName.value,
|
||||
};
|
||||
console.log(data)
|
||||
axios.patch(`/api/devices/${currentDevice.value?.UUID}/experts/${selectExpert.value}`, data).then((resp: AxiosResponse) => {
|
||||
console.log(resp.data)
|
||||
loadDevices();
|
||||
setTimeout(closeModal, 500)
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<dialog id="show_device_update_modal" class="modal">
|
||||
<div class="modal-box">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
|
||||
<h3 class="font-bold text-lg">{{ currentDevice?.UUID }}</h3>
|
||||
<div class="py-5">
|
||||
<label class="form-control w-full max-w-xs">
|
||||
<div class="label">
|
||||
<span class="label-text">设备名称</span>
|
||||
</div>
|
||||
<input type="text" placeholder="设备名称" class="input input-bordered w-full max-w-xs" v-model="deviceName"/>
|
||||
</label>
|
||||
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text">选择专家</span>
|
||||
</div>
|
||||
<select class="select select-bordered w-full max-w-xs" v-model="selectExpert">
|
||||
<option v-for="expert in experts" :key="expert.ID" :value="expert.UID">{{ expert.RealName }}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3">
|
||||
<button class="btn btn-wide btn-primary" @click="saveDate">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<div v-if="devices.length == 0">
|
||||
<h1 class="text-lg text-center">还没有设备</h1>
|
||||
</div>
|
||||
|
||||
<table className="table" v-else>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>设备号</th>
|
||||
<th>名称</th>
|
||||
<th>专家</th>
|
||||
<th>停止关注</th>
|
||||
<th>备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr :class='idx % 2 == 1 ? "bg-slate-50" : ""' v-for="(item, idx) in devices" :key="item.ID">
|
||||
<td class="flex flex-col">
|
||||
<div class="text-lg font-semibold">{{ item.UUID }}</div>
|
||||
</td>
|
||||
<td>{{ item.Name }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm" @click="showDeviceUpdateModal(item.ID)">{{ item.ExpertName }}</button>
|
||||
</td>
|
||||
<td>
|
||||
<button v-if="item.State == 'stop'" class="btn btn-warning btn-sm"
|
||||
@click="resetStateNormal(item.ID)">恢复</button>
|
||||
<button v-else class="btn btn-error btn-sm text-white" @click="setStateStop(item.ID)">停止</button>
|
||||
</td>
|
||||
<td>{{ item.Note }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
288
frontend/src/components/Experts.vue
Normal file
288
frontend/src/components/Experts.vue
Normal file
@@ -0,0 +1,288 @@
|
||||
<script setup lang="ts">
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import moment from 'moment';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
interface Expert {
|
||||
ID: number
|
||||
UID: string
|
||||
SecUID: string
|
||||
ShortID: string
|
||||
RealName: string
|
||||
NickName: string
|
||||
State: string
|
||||
Since: number
|
||||
Focus: number
|
||||
Total: number
|
||||
Conf: Conf
|
||||
}
|
||||
|
||||
interface Conf {
|
||||
Voice: string
|
||||
Hello: string
|
||||
Wechat: string
|
||||
Region: string[]
|
||||
NameKeyword: string[]
|
||||
Produce: boolean
|
||||
DefaultName: boolean
|
||||
DefaultAvatar: boolean
|
||||
}
|
||||
|
||||
|
||||
const experts = ref<Expert[]>([]);
|
||||
|
||||
// use axios get /experts onMount
|
||||
const loadExperts = function () {
|
||||
axios.get<Expert[]>('/api/experts').then((resp: AxiosResponse) => {
|
||||
experts.value = resp.data;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(loadExperts);
|
||||
setInterval(loadExperts, 30 * 1000);
|
||||
|
||||
|
||||
const parseTime = function (timestamp: number) {
|
||||
if (timestamp == 0) {
|
||||
return '不限制'
|
||||
}
|
||||
|
||||
return moment.unix(timestamp).format('YY/MM/DD HH:mm:ss')
|
||||
}
|
||||
const currentExpert = ref<Expert | null>(null);
|
||||
|
||||
const showExpertDateModal = (id: number) => {
|
||||
currentExpert.value = experts.value.find((expert) => expert.ID === id) || null;
|
||||
|
||||
date.value = moment.unix(currentExpert.value?.Since || 0).format('YYYY-MM-DDTHH:mm');
|
||||
|
||||
voice.value = currentExpert.value?.Conf.Voice || '';
|
||||
hello.value = currentExpert.value?.Conf.Hello || '';
|
||||
wechat.value = currentExpert.value?.Conf.Wechat || '';
|
||||
region.value = (currentExpert.value?.Conf.Region || []).join(',');
|
||||
produce.value = currentExpert.value?.Conf.Produce || false;
|
||||
default_name.value = currentExpert.value?.Conf.DefaultName || false;
|
||||
default_avatar.value = currentExpert.value?.Conf.DefaultAvatar || false;
|
||||
name_keyword.value = (currentExpert.value?.Conf.NameKeyword || []).join(',');
|
||||
|
||||
|
||||
const dialog = document.getElementById("set_date_modal") as HTMLDialogElement;
|
||||
dialog.showModal();
|
||||
};
|
||||
|
||||
const resetStateNormal = (id: number) => {
|
||||
currentExpert.value = experts.value.find((expert) => expert.ID === id) || null;
|
||||
if (!currentExpert.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// alert to confirm
|
||||
if (!confirm(`确定要恢复 ${currentExpert.value.RealName} 的自动关注吗?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = { state: "" };
|
||||
axios.patch(`/api/experts/${currentExpert.value?.UID}/state`, data).then((resp: AxiosResponse) => {
|
||||
console.log(resp.data)
|
||||
loadExperts();
|
||||
});
|
||||
};
|
||||
|
||||
const setStateStop = (id: number) => {
|
||||
currentExpert.value = experts.value.find((expert) => expert.ID === id) || null;
|
||||
if (!currentExpert.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// alert to confirm
|
||||
if (!confirm(`确定要停止 ${currentExpert.value.RealName} 的自动关注吗?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = { state: "stop" };
|
||||
axios.patch(`/api/experts/${currentExpert.value?.UID}/state`, data).then((resp: AxiosResponse) => {
|
||||
console.log(resp.data)
|
||||
loadExperts();
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = function () {
|
||||
const dialog = document.getElementById("set_date_modal") as HTMLDialogElement;
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
const resetDate = () => {
|
||||
const data = { since: 0 };
|
||||
axios.patch(`/api/experts/${currentExpert.value?.UID}/config`, data).then((resp: AxiosResponse) => {
|
||||
console.log(resp.data)
|
||||
loadExperts();
|
||||
setTimeout(closeModal, 500)
|
||||
});
|
||||
};
|
||||
|
||||
const date = ref('2022-02-01T01:10');
|
||||
const voice = ref('');
|
||||
const hello = ref('');
|
||||
const wechat = ref('');
|
||||
const region = ref('');
|
||||
const produce = ref(false);
|
||||
const default_name = ref(false);
|
||||
const default_avatar = ref(false);
|
||||
const name_keyword = ref('');
|
||||
|
||||
const saveDate = () => {
|
||||
let regions = region.value.replace(/\s/g, '').replace(",", "").split(',');
|
||||
let name_keywords = name_keyword.value.replace(/\s/g, '').replace(",", "").split(',');
|
||||
|
||||
const data = {
|
||||
since: Date.parse(date.value) / 1000,
|
||||
voice: voice.value,
|
||||
hello: hello.value,
|
||||
wechat: wechat.value,
|
||||
region: regions,
|
||||
produce: produce.value,
|
||||
DefaultName: default_name.value,
|
||||
DefaultAvatar: default_avatar.value,
|
||||
NameKeyword: name_keywords,
|
||||
};
|
||||
console.log(data)
|
||||
axios.patch(`/api/experts/${currentExpert.value?.UID}/config`, data).then((resp: AxiosResponse) => {
|
||||
console.log(resp.data)
|
||||
loadExperts();
|
||||
setTimeout(closeModal, 500)
|
||||
});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<dialog id="set_date_modal" class="modal">
|
||||
<div class="modal-box">
|
||||
<form method="dialog">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
|
||||
<h3 class="font-bold text-lg">{{ currentExpert?.RealName }}</h3>
|
||||
<div class="py-5">
|
||||
|
||||
<label class="form-control w-full mb-5">
|
||||
<div class="label">
|
||||
<span class="label-text">微信号</span>
|
||||
</div>
|
||||
<input type="text" placeholder="微信号" class="input input-bordered w-full " v-model="wechat" />
|
||||
</label>
|
||||
|
||||
<label class="form-control w-full mb-5">
|
||||
<div class="label">
|
||||
<span class="label-text">语音ID</span>
|
||||
</div>
|
||||
<input type="text" placeholder="语音ID" class="input input-bordered w-full " v-model="voice" />
|
||||
</label>
|
||||
|
||||
<label class="form-control w-full " mb-5>
|
||||
<div class="label">
|
||||
<span class="label-text">打招呼模板</span>
|
||||
</div>
|
||||
<textarea v-model="hello" class="textarea textarea-bordered w-full " placeholder="打招呼模板"></textarea>
|
||||
</label>
|
||||
|
||||
<label class="form-control w-full mb-5">
|
||||
<div class="label">
|
||||
<span class="label-text">屏蔽IP区域用户</span>
|
||||
</div>
|
||||
<input type="text" placeholder="屏蔽IP区域用户" class="input input-bordered w-full" v-model="region" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt">多个区域用 , 号分割</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form-control w-full mb-5">
|
||||
<div class="label">
|
||||
<span class="label-text">屏蔽用户名关键字</span>
|
||||
</div>
|
||||
<input type="text" placeholder="屏蔽用户名关键字" class="input input-bordered w-full" v-model="name_keyword" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt">多个用,号分割</span>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form-control w-full ">
|
||||
<div class="form-control">
|
||||
<label class="cursor-pointer label justify-start gap-1">
|
||||
<input type="checkbox" v-model="produce" class="checkbox checkbox-success" />
|
||||
<span class="label-text">屏蔽0作品用户</span>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form-control w-full ">
|
||||
<div class="form-control">
|
||||
<label class="cursor-pointer label justify-start gap-1">
|
||||
<input type="checkbox" v-model="default_name" class="checkbox checkbox-success" />
|
||||
<span class="label-text">屏蔽默认用户名用户</span>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
|
||||
<label class="form-control w-full ">
|
||||
<div class="form-control">
|
||||
<label class="cursor-pointer label justify-start gap-1">
|
||||
<input type="checkbox" v-model="default_avatar" class="checkbox checkbox-success" />
|
||||
<span class="label-text">屏蔽默认头像名用户</span>
|
||||
</label>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form-control w-full">
|
||||
<div class="label">
|
||||
<span class="label-text">选择时间</span>
|
||||
</div>
|
||||
<input type="datetime-local" v-model="date" placeholder="请选择时间" class="input input-bordered w-full" />
|
||||
<div class="label">
|
||||
<span class="label-text-alt">获取晚于这个时间关注的粉丝数据</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex justify-end gap-3">
|
||||
<button class="btn btn-default" @click="resetDate">恢复不限制</button>
|
||||
<button class="btn btn-wide btn-primary" @click="saveDate">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<div v-if="experts.length == 0">
|
||||
<h1 class="text-lg text-center">还没有专家</h1>
|
||||
</div>
|
||||
|
||||
<table className="table" v-else>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>专家</th>
|
||||
<th>已关注/所有</th>
|
||||
<th>配置</th>
|
||||
<th>停止关注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
<tr :class='idx % 2 == 1 ? "bg-slate-50" : ""' v-for="(item, idx) in experts" :key="item.ID">
|
||||
<td class="flex flex-col">
|
||||
<div class="text-lg font-semibold">{{ item.RealName }}</div>
|
||||
<div>{{ item.UID }}</div>
|
||||
</td>
|
||||
<td>{{ item.Focus }} / {{ item.Total }}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm" @click="showExpertDateModal(item.ID)">{{ parseTime(item.Since) }}</button>
|
||||
</td>
|
||||
<td>
|
||||
<button v-if="item.State == 'stop'" class="btn btn-warning btn-sm"
|
||||
@click="resetStateNormal(item.ID)">恢复</button>
|
||||
<button v-else class="btn btn-error btn-sm text-white" @click="setStateStop(item.ID)">停止</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user