| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- <template>
- <div class="container">
- <Breadcrumb :items="['网站管理', '乐跑账号管理']" />
- <a-card title="乐跑账号管理">
- <a-row>
- <a-col :flex="1">
- <a-form :model="queryData" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }"
- label-align="left">
- <a-row :gutter="16">
- <a-col :span="6">
- <a-form-item field="area" label="跑区">
- <a-select v-model="queryData.area" placeholder="请选择乐跑跑区" default-value="">
- <a-option value="">所有</a-option>
- <a-option v-for="(item, index) in area" :key="index" :value="item">
- {{ item }}
- </a-option>
- </a-select>
- </a-form-item>
- </a-col>
- <a-col :span="6">
- <a-form-item field="user_uuid" label="创建人UUID">
- <a-input v-model="queryData.user_uuid" allow-clear />
- </a-form-item>
- </a-col>
- <a-col :span="6">
- <a-form-item field="email" label="用户邮箱">
- <a-input v-model="queryData.email" allow-clear />
- </a-form-item>
- </a-col>
- <a-col :span="6">
- <a-form-item field="username" label="账号名称">
- <a-input v-model="queryData.username" allow-clear />
- </a-form-item>
- </a-col>
- <a-col :span="6">
- <a-form-item field="student_num" label="学号">
- <a-input v-model="queryData.student_num" allow-clear />
- </a-form-item>
- </a-col>
- <a-col :span="6">
- <a-form-item field="state" label="账号状态">
- <a-select v-model="queryData.state" :options="state" placeholder="请选择账号状态"
- :default-value="-1" />
- </a-form-item>
- </a-col>
- <a-col :span="6">
- <a-form-item field="auto_time" label="乐跑时段">
- <a-select v-model="queryData.auto_time" placeholder="请选择自动乐跑时段" :default-value="0">
- <a-option :value="0">所有时段</a-option>
- <a-option v-for="(item, index) in auto_time" :key="index" :value="item.value">
- {{ item.label }}
- </a-option>
- </a-select>
- </a-form-item>
- </a-col>
- <a-col :span="6">
- <a-form-item field="queryTime" label="更新时间">
- <a-range-picker v-model="queryData.queryTime" show-time format="YY-MM-DD HH:mm"
- value-format="x" @ok="search()" />
- </a-form-item>
- </a-col>
- </a-row>
- </a-form>
- </a-col>
- <a-divider style="height: 84px" direction="vertical" />
- <a-col :flex="'86px'" style="text-align: right">
- <a-space direction="vertical" :size="18">
- <a-button type="primary" @click="search">
- <template #icon>
- <icon-search />
- </template>
- 搜索
- </a-button>
- <a-button @click="reset">
- <template #icon>
- <icon-refresh />
- </template>
- 重置
- </a-button>
- </a-space>
- </a-col>
- </a-row>
- <a-table :data="data" :bordered="false" class="table" :loading="loading" :columns="columns" :pagination="{
- showPageSize: true,
- showJumper: true,
- showTotal: true,
- pageSize: pagination.pagesize,
- current: pagination.current,
- total: pagination.total
- }" @page-change="handlePageChange" @page-size-change="handlePageSizeChange" >
- <template #create_user="{ record }">
- <a-avatar :size="30">
- <img :alt="record.create_user ?? ''"
- :src="record.avatar || 'https://lepao-cloud.xxoo365.top/view.php/25aa126dc406974ff3579a99a2c6501a.png'" />
- </a-avatar>
- {{ record.create_user }}
- </template>
- <template #name="{ record }">
- <a-avatar :size="30">
- <img :alt="record.name ?? ''"
- :src="record.user_avatar || 'https://lepao-cloud.xxoo365.top/view.php/25aa126dc406974ff3579a99a2c6501a.png'" />
- </a-avatar>
- {{ record.name }}
- </template>
- <template #sex="{ record }">
- <icon-man v-if="record.sex === 1" />
- <icon-woman v-else-if="record.sex === 2" />
- {{ record.sex === 1 ? '男' : (record.sex === 2 ? '女' : '') }}
- </template>
- <template #auto_run="{ record }">
- <a-tag color="green" v-if="record.auto_run">{{ record.target_count === 0 ? '开启-∞次' :
- `开启-${record.target_count}次` }}</a-tag>
- <a-tag color="red" v-else>关闭</a-tag>
- </template>
- <template #auto_day="{ record }">
- <span v-if="record.auto_run && record.auto_day && record.auto_day.length > 0">
- {{record.auto_day.slice().sort((a, b) => {
- if (a === 0) return 1; if (b === 0) return -1; return a - b;
- }).map(day => auto_day.find(item => item.value === day)?.label).join(',').replace(/周/g, '')}}
- </span>
- <span v-else>-</span>
- </template>
- <template #num="{ record }">
- {{ record.term_num != record.total_num ? `${record.total_num} / ${record.term_num}` :
- '已完成' }}
- </template>
- <template #state="{ record }">
- <div v-if="record.state === 0" class="state">
- <div class="circle zero"></div>需登录
- </div>
- <div v-else-if="record.state === 1" class="state">
- <div class="circle one"></div>正常
- </div>
- <div v-else class="state">
- <div class="circle else"></div>状态异常
- </div>
- </template>
- <template #face_state="{ record }">
- <div v-if="record.face_state === 0" class="state">
- <div class="circle zero"></div>未采集
- </div>
- <div v-else-if="record.face_state === 1" class="state">
- <div class="circle one"></div>已通过
- </div>
- <div v-else class="state">
- <div class="circle else"></div>不通过
- </div>
- </template>
- <template #auto_time="{ record }">
- {{ autoTimeLabel(record) }}
- </template>
- <template #create_time="{ record }">
- {{ stramptoTime(record.create_time) }}
- </template>
- <template #update_time="{ record }">
- {{ record.update_time ? stramptoTime(record.update_time) : '待登录' }}
- </template>
- <template #optional="{ record }">
- <a-dropdown :popup-max-height="false" trigger="hover">
- <a-button>操作 <icon-down /></a-button>
- <template #content>
- <a-doption @click="editAccount(record)"><icon-edit /> 编辑账号</a-doption>
- <a-doption @click="faceRecoRef.openModal(record)"><icon-video-camera /> 人脸采集</a-doption>
- <a-doption @click="SingleRun(record)"><icon-play-circle /> 开始单次乐跑</a-doption>
- <a-doption @click="ChangeAutoRun(record)"><icon-translate /> {{ record.auto_run ? '关闭' :
- '开启' }}自动乐跑</a-doption>
- <a-doption @click="DeleteAccount(record)"><icon-minus-circle /> 解绑账号</a-doption>
- </template>
- </a-dropdown>
- </template>
- </a-table>
- </a-card>
- </div>
- <!-- 账号编辑对话框 -->
- <a-modal v-model:visible="visible" title="编辑账号" @cancel="handleCancel" @before-ok="handleBeforeOk" draggable
- :ok-loading="ok_loading" esc-to-close closable>
- <a-form :model="form">
- <a-form-item field="student_num" label="学号">
- <a-input v-model="form.student_num" placeholder="账号所有者学号,填写错误将无法登录" />
- </a-form-item>
- <a-form-item field="email" label="通知邮箱">
- <a-input v-model="form.email" placeholder="用于接收乐跑失败、登录失效的通知" />
- </a-form-item>
- <a-form-item field="area" label="乐跑跑区">
- <a-select v-model="form.area" placeholder="请选择乐跑跑区" default-value="">
- <a-option value="">随机分配</a-option>
- <a-option v-for="(item, index) in area" :key="index" :value="item">
- <span class="vipcontent">
- <span>{{ item }} </span>
- </span>
- </a-option>
- </a-select>
- </a-form-item>
- <a-form-item field="auto_run" label="自动乐跑开关">
- <a-switch v-model="form.auto_run" :checked-value="1" :unchecked-value="0" />
- </a-form-item>
- <a-form-item field="target_count" label="乐跑目标次数" v-if="form.auto_run">
- <a-input-number v-model="form.target_count" placeholder="请输入乐跑目标次数" mode="button" />
- <template #extra>
- <div>当学期有效次数达到目标次数时自动乐跑将关闭,0为不限次</div>
- </template>
- </a-form-item>
- <a-form-item field="auto_day" label="自动乐跑星期" v-if="form.auto_run">
- <a-checkbox-group v-model="form.auto_day" placeholder="请选择每天自动乐跑的星期" :options="auto_day" />
- </a-form-item>
- <a-form-item field="auto_time" label="自动乐跑时段" v-if="form.auto_run">
- <a-select v-model="form.auto_time" placeholder="请选择每天自动乐跑的时段" :options="auto_time" />
- </a-form-item>
- <a-form-item field="notes" label="备注">
- <a-textarea v-model="form.notes" placeholder="添加对账号的备注(非必填)" :max-length="{ length: 50 }" allow-clear
- show-word-limit />
- </a-form-item>
- </a-form>
- </a-modal>
- <faceModal :faceInfo="faceInfo" ref="faceRecoRef" />
- </template>
- <script setup>
- import { ref, reactive, onMounted, h } from 'vue'
- import { addAccount, adminAccountList, deleteAccount, changeAutoRun, singleRun } from '@/api/lepao'
- import { Modal, Notification, Message } from '@arco-design/web-vue'
- import faceModal from '@/components/FaceModal/faceModal.vue'
- import { getSemesterTimestamps } from '@/utils/util'
- const faceRecoRef = ref(null)
- const queryData = reactive({
- area: '',
- student_num: '',
- user_uuid: '',
- email: '',
- username: '',
- state: -1,
- auto_time: 0,
- queryTime: []
- })
- const pagination = reactive({
- total: 0,
- current: 1, // 默认从第1页开始
- pagesize: 20
- })
- const visible = ref(false)
- const ok_loading = ref(false)
- const form = reactive({
- id: null,
- student_num: '',
- email: '',
- area: '',
- auto_run: 1,
- auto_time: -1,
- target_count: 30,
- auto_day: [0, 1, 2, 3, 4, 5, 6],
- notes: ''
- })
- const loading = ref(false)
- const data = ref([])
- const auto_day = [
- { label: '周一', value: 1 },
- { label: '周二', value: 2 },
- { label: '周三', value: 3 },
- { label: '周四', value: 4 },
- { label: '周五', value: 5 },
- { label: '周六', value: 6 },
- { label: '周日', value: 0 }
- ]
- const state = [
- { label: '全部', value: -1 }, { label: '需登录', value: 0 }, { label: '正常', value: 1 }, { label: '状态异常', value: 2 }
- ]
- const area = ["兰花湖校区跑区", "主校区北跑区", "主校区南跑区", "重庆工商大学茶园校区"]
- const columns = [
- {
- title: 'ID',
- dataIndex: 'id',
- fixed: 'left',
- width: 65
- }, {
- title: '创建用户',
- slotName: 'create_user',
- width: 160
- }, {
- title: '账号名称',
- slotName: 'name',
- width: 180
- }, {
- title: '学号',
- dataIndex: 'student_num',
- width: 115
- }, {
- title: '性别',
- slotName: 'sex',
- width: 80
- }, {
- title: '学院',
- dataIndex: 'academy_name',
- width: 200
- }, {
- title: '年级',
- dataIndex: 'grade_id',
- width: 80
- }, {
- title: '通知邮箱',
- dataIndex: 'email',
- width: 200
- },
- {
- title: '乐跑跑区',
- dataIndex: 'area',
- width: 150
- }, {
- title: '自动乐跑',
- slotName: 'auto_run',
- width: 90
- }, {
- title: '自动乐跑星期',
- slotName: 'auto_day',
- width: 155
- }, {
- title: '自动乐跑时段',
- slotName: 'auto_time',
- width: 130
- }, {
- title: '学期目标',
- slotName: 'num',
- width: 90
- }, {
- title: '帐号状态',
- slotName: 'state',
- width: 100
- }, {
- title: '手机型号',
- dataIndex: 'deviceModel',
- width: 100
- }, {
- title: 'UA',
- dataIndex: 'userAgent',
- width: 120,
- ellipsis: true,
- tooltip: true
- }, {
- title: '人脸状态',
- slotName: 'face_state',
- width: 100
- }, {
- title: '添加时间',
- slotName: 'create_time',
- width: 150
- }, {
- title: '上次更新时间',
- slotName: 'update_time',
- width: 150
- }, {
- title: '备注',
- dataIndex: 'notes',
- width: 200
- }, {
- title: '操作',
- slotName: 'optional',
- fixed: 'right',
- width: 90
- }]
- const search = () => {
- pagination.current = 1
- getAccounts()
- }
- const reset = () => {
- queryData.area = ''
- queryData.student_num = ''
- queryData.user_uuid = ''
- queryData.email = ''
- queryData.username = ''
- queryData.state = -1
- queryData.auto_time = 0
- queryData.queryTime = getSemesterTimestamps()
- getAccounts()
- }
- const getAccounts = async () => {
- try {
- loading.value = true
- const reqData = {
- ...queryData,
- pagesize: pagination.pagesize,
- current: pagination.current
- }
- const res = await adminAccountList(reqData)
- if (!res || res.code !== 0)
- return Notification.error({
- title: '获取账号数据失败!',
- content: res?.msg ?? '请稍后再试'
- })
- data.value = res.data
- pagination.total = res.pagination.total
- } catch (error) {
- Notification.error({
- title: '获取账号数据失败!',
- content: error.message || '请稍后再试'
- })
- } finally {
- loading.value = false
- }
- }
- const editAccount = (item) => {
- if (item) {
- form.id = item.id
- form.student_num = item.student_num
- form.email = item.email
- form.area = item.area
- form.auto_run = item.auto_run
- form.auto_time = item.auto_time
- form.auto_day = item.auto_day
- form.target_count = item.target_count
- form.notes = item.notes
- } else {
- form.id = null
- form.student_num = ''
- form.auto_run = 1
- form.auto_time = 7
- form.target_count = 30
- form.auto_day = [0, 1, 2, 3, 4, 5, 6]
- form.email = email.value
- form.notes = ''
- }
- visible.value = true
- }
- const handleBeforeOk = async (done) => {
- try {
- ok_loading.value = true
- const { student_num, email } = form
- if (!student_num || !email) {
- Message.error('请填写完整的账号信息')
- return false
- }
- const studentNumRegex = /^\d{10}$/
- if (!studentNumRegex.test(student_num)) {
- Message.error('请检查学号格式是否正确')
- return false
- }
- const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
- if (!emailRegex.test(email)) {
- Message.error('请检查邮箱格式是否正确')
- return false
- }
- let data = {
- ...form
- }
- const res = await addAccount(data)
- if (!res || res.code !== 0) {
- Notification.error({
- title: '保存乐跑账号失败!',
- content: res?.msg ?? '请稍后再试'
- })
- return false
- }
- Message.success('保存成功!')
- done()
- getAccounts()
- } catch (error) {
- Notification.error({
- title: '保存乐跑账号失败!',
- content: error.message || '请稍后再试'
- })
- return false
- } finally {
- ok_loading.value = false
- }
- }
- const handleCancel = () => {
- visible.value = false;
- }
- // 分页 - 页码变化
- const handlePageChange = (page) => {
- pagination.current = page
- getAccounts()
- }
- // 分页 - 每页条数变化
- const handlePageSizeChange = (size) => {
- pagination.pagesize = size
- pagination.current = 1 // 页大小变化后回到第一页
- getAccounts()
- }
- const DeleteAccount = async (item) => {
- Modal.confirm({
- title: '解绑账号',
- content: () => h('div', [
- h('p', '您是否要解绑该账号?该操作不可逆')
- ]),
- onOk: async () => {
- const res = await deleteAccount({ id: item.id })
- if (!res || res.code !== 0)
- return Notification.error({
- title: '解绑失败',
- content: res?.msg ?? '请稍后再试'
- })
- Message.success('解绑成功!')
- getAccounts()
- }
- })
- }
- const ChangeAutoRun = async (record) => {
- const oldValue = record.auto_run;
- record.auto_run = oldValue === 1 ? 0 : 1;
- try {
- const res = await changeAutoRun({ id: record.id });
- if (!res || res.code !== 0) {
- record.auto_run = oldValue;
- Notification.error({
- title: '切换自动乐跑状态失败!',
- content: res?.msg ?? '请稍后再试'
- })
- } else {
- Message.success(`自动乐跑状态已${record.auto_run === 1 ? '开启' : '关闭'}`)
- getAccounts()
- }
- } catch (error) {
- console.log(error)
- record.auto_run = oldValue
- Message.error('切换自动乐跑状态失败!')
- }
- }
- const SingleRun = async (item) => {
- if (item.state !== 1)
- return Notification.warning({
- title: '当前乐跑账号需登录,请登录后再试',
- content: '如有疑问请联系客服'
- })
- Modal.confirm({
- title: '开始乐跑',
- content: () => h('div', [
- h('p', `您是否要为${item.name}(${item.student_num})乐跑?若乐跑成功将扣减乐跑次数`)
- ]),
- onOk: async () => {
- const res = await singleRun({ student_num: item.student_num })
- if (!res || res.code !== 0)
- return Notification.error({
- title: '提交乐跑任务失败',
- content: res?.msg ?? '请稍后再试'
- })
- Message.success('提交乐跑任务成功!')
- }
- })
- }
- onMounted(() => {
- queryData.queryTime = getSemesterTimestamps()
- getAccounts()
- })
- const stramptoTime = (time) => {
- return new Date(time).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
- }
- const auto_time = [
- { label: '随机', value: -1 },
- ...Array.from({ length: 17 }, (_, i) => {
- const hour = i + 7
- return { label: `${hour} ~ ${hour + 1}时`, value: hour }
- })
- ]
- const autoTimeLabel = (record) => {
- if (record.auto_time === -1 && record.today_auto_time) {
- return `随机-今日${record.today_auto_time}时`
- }
- const match = auto_time.find(item => item.value === record.auto_time)
- return match ? match.label : '-'
- }
- </script>
- <style scoped>
- .container {
- padding: 0 20px 20px 20px;
- }
- .table {
- font-family: -apple-system, BlinkMacSystemFont;
- margin-top: 15px;
- .state {
- display: flex;
- align-items: center;
- .circle {
- border-radius: 50%;
- height: 8px;
- min-height: 8px;
- width: 8px;
- min-width: 8px;
- margin-right: 5px;
- }
- .zero {
- background-color: rgb(var(--orange-6));
- }
- .one {
- background-color: rgb(var(--green-6));
- }
- .else {
- background-color: rgb(var(--red-6));
- }
- }
- }
- </style>
|