Commit 706c5c7c authored by daniel's avatar daniel

feat(info): 信息收集后台开发

parent 6cdc86e2
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
, a.id pathId , a.id pathId
, a.path_name pathName , a.path_name pathName
, a.create_time createTime , a.create_time createTime
, a.sort , a.sort
from gk_roads_gps a from gk_roads_gps a
WHERE 1=1 WHERE 1=1
AND a.is_delete = 0 AND a.is_delete = 0
......
package com.dlbridge.jadp.plugins.cmpt.web;
import com.dlbridge.jadp.common.interceptor.PageView;
import com.dlbridge.jadp.plugins.cmpt.dao.model.Cmpt;
import com.dlbridge.jadp.plugins.cmpt.service.CmptService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
@RestController
@RequestMapping("gk/gpsDeposit")
@Api(value = "GPS押金服务", tags = "GPS押金服务")
public class GpsDepositController {
@Resource
private CmptService cmptService;
@GetMapping("index/{cmptId}")
@PageView
@ApiOperation(value = "页面路径", notes = "页面路径")
public ModelAndView modulePage(@PathVariable("cmptId") Integer cmptId, ModelAndView mv) {
Cmpt cmpt = cmptService.queryCmptBycmptId(cmptId);
mv.addObject("cmpt", cmpt);
mv.setViewName("cmpt/gpsDeposit");
return mv;
}
}
package com.dlbridge.jadp.plugins.cmpt.web;
import com.dlbridge.jadp.common.interceptor.PageView;
import com.dlbridge.jadp.plugins.cmpt.dao.model.Cmpt;
import com.dlbridge.jadp.plugins.cmpt.service.CmptService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
@RestController
@RequestMapping("gk/information")
@Api(value = "信息收集服务", tags = "信息收集服务")
public class InformationController {
@Resource
private CmptService cmptService;
@GetMapping("index/{cmptId}")
@PageView
@ApiOperation(value = "页面路径", notes = "页面路径")
public ModelAndView modulePage(@PathVariable("cmptId") Integer cmptId, ModelAndView mv) {
Cmpt cmpt = cmptService.queryCmptBycmptId(cmptId);
mv.addObject("cmpt", cmpt);
mv.setViewName("cmpt/information");
return mv;
}
}
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="fns" uri="/WEB-INF/tlds/fns.tld" %>
<%@ taglib prefix="fnc" uri="/WEB-INF/tlds/fnc.tld" %>
<c:set var="ctx" value="${pageContext.request.contextPath}${fns:getAdminPath()}" />
<c:set var="path" value="${pageContext.request.contextPath}${fns:getPath()}" />
<c:set var="jaaproot" value="${pageContext.request.contextPath}" />
<c:set var="ctxStatic" value="${pageContext.request.contextPath}/static" />
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>自助选号</title>
<!-- ... 保留原有的 Vue 引入 ... -->
<script src="${ctxStatic}/vue/vue.global.min.js"></script>
<!-- 添加 Naive UI 的引入 -->
<!-- 会使用最新版本,你最好指定一个版本 -->
<script src="${ctxStatic}/navie-ui/index.min.js"></script>
<!-- <script nomodule
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script> -->
<script type="module"
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
<!-- Favicon-->
<link rel="shortcut icon" href="https://img.geexek.com/files/home/favicon.ico">
<!-- 自定义css -->
<link rel="stylesheet" href="${ctxStatic}/css/base.css">
<!-- Bootstrap CSS-->
<link rel="stylesheet"
href="${ctxStatic}/newdermon/vendor/bootstrap/css/bootstrap.min.css">
<!-- theme stylesheet-->
<link rel="stylesheet"
href="${ctxStatic}/newdermon/css/style.default.css?v=<%=System.currentTimeMillis()%>"
id="theme-stylesheet">
<!-- Custom stylesheet - for your changes-->
<link rel="stylesheet"
href="${ctxStatic}/newdermon/css/custom.css?v=<%=System.currentTimeMillis()%>">
<!-- Favicon-->
<link rel="shortcut icon" href="https://img.geexek.com/files/home/favicon.ico">
<link rel="stylesheet"
href="${ctxStatic}/cmpt/setting.css?v=<%=System.currentTimeMillis()%>">
<script src="${ctxStatic}/newdermon/vendor/jquery/jquery.min.js"></script>
<!-- 引入富文本 -->
<script
src="https://enroll.geexek.com/js/ueditor/ueditor.config-zs.js?v=<%=System.currentTimeMillis()%>"></script>
<script
src="https://enroll.geexek.com/js/ueditor/ueditor.all.js?v=<%=System.currentTimeMillis()%>"></script>
<style>
#app {
padding: 0 20px 20px 20px;
}
.layout {
min-height: 100vh;
}
.carousel-img {
width: 100%;
height: 240px;
object-fit: cover;
}
[v-cloak] {
display: none;
}
.n-message .n-message__icon>* {
position: absolute;
left: 0;
top: -5px !important;
right: 0;
bottom: 0;
}
.n-dialog__icon>* {
position: absolute;
left: 0;
top: -2px !important;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<jsp:include page="../common/header.jsp"></jsp:include>
<div class="page-content d-flex align-items-stretch main-body">
<jsp:include page="../common/menu.jsp"></jsp:include>
<div id="app" class="flex-1" v-cloak>
<n-config-provider :locale="locale" :date-locale="dateLocale">
<n-modal-provider>
<n-message-provider>
<n-notification-provider>
<my-app></my-app>
</n-notification-provider>
</n-message-provider>
</n-modal-provider>
</n-config-provider>
</div>
</div>
<script type="text/x-template" id="my-app">
<n-space vertical >
<!-- 顶部的搜索框 -->
<n-card :bordered="false">
<div class="w-full flex flex-row items-center gap-x-2">
<div style="min-width: 200px">
<n-tabs type="segment" @update:value="updateTab":value="tabIndex" animated pane-style="padding:0;">
<n-tab-pane v-for="item in TabEnum.data" :name="item.value" :tab="item.name"/>
</n-tabs>
</div>
<div style="min-width: 400px" v-if="!isSetting" >
<n-tabs type="segment"
@update:value="updateStatus" :value="query.status" animated pane-style="padding:0;">
<n-tab-pane
v-for="item in OrderStatusOptions"
:name="item.value"
:tab="item.name+'('+item.total+')'"/>
</n-tabs>
</div>
<div class="ml-auto" v-if="!isSetting">
<n-input v-model:value="keyword" placeholder="请输入关键字进行搜索" :clearable="true" @change="fetchData" />
</div>
</div>
<div class="w-full gap-x-2 flex flex-row items-center mt-3">
<div class="flex-1" v-if="!isSetting" >
<n-tabs type="segment"
@update:value="updateStatus" :value="query.status" animated pane-style="padding:0;">
<n-tab-pane
v-for="item in GpsStatusOptions"
:name="item.value"
:tab="item.name+'('+item.total+')'"/>
</n-tabs>
</div>
<n-space>
<n-button type="primary" >
导出未交押金
</n-button>
<n-button type="primary" >
导出已交押金
</n-button>
<n-button>
订单下载
</n-button>
<n-button>
一键归还
</n-button>
<n-button v-if="checkedRowKeys.length" type="error">
批量退款
</n-button>
</n-space>
</div>
</n-card>
<!-- 搜索框 -->
<n-data-table v-if="!isSetting" :columns="playersColumns" :loading="loading" :row-key="rowKey" :data="list" :pagination="pagination"
:bordered="false"
:remote="true" @update:checked-row-keys="handleCheck"/>
<n-card v-else title="押金详情" :bordered="false" >
<n-form
ref="depositFormRef"
:model="depositForm"
:rules="depositRules"
label-placement="left"
label-width="auto"
>
<n-form-item label="组别" path="roadIds">
<n-select v-model:value="depositForm.roadIds" multiple :options="roadOptions" />
</n-form-item>
<n-form-item label="押金金额" path="money">
<n-input-number v-model:value="depositForm.money" placeholder="请输入押金金额" />
</n-form-item>
<n-form-item label="状态">
<n-switch
:checked-value="1"
:unchecked-value="0"
v-model:value="depositForm.status"
>
<template #checked>
开启
</template>
<template #unchecked>
关闭
</template>
</n-switch>
</n-form-item>
<n-form-item label="公告" >
<n-input type="textarea" v-model:value="depositForm.notice" placeholder="请输入公告" />
</n-form-item>
<n-form-item label="内容" >
<div class="w-full" id="rich-text-container" ></div>
</n-form-item>
</n-form>
<template #header-extra>
<n-button class="flex-1"
:loading="loading" type='primary' @click="onSubmit">
保存
</n-button>
</template>
</n-card>
<n-modal
v-model:show="showModal"
preset="dialog"
title="确认"
content="是否确定开始随机分配?"
positive-text="确认"
negative-text="取消"
@positive-click="randomAssign"
/>
<n-modal
v-model:show="showBatchModal"
preset="dialog"
title="批量退款"
content="是否确定对选中的用户进行退款?"
positive-text="确认"
negative-text="取消"
@positive-click="batchRefund"
/>
</n-space>
</script>
<script>
const cmpt = JSON.parse('${fns:toJson(cmpt)}');
console.log({ cmpt })
// 枚举class
class SuperEnum {
constructor(param) {
const keyObj = {};
const valueObj = {};
const nameObj = {};
if (!Array.isArray(param)) {
throw new TypeError('param is not an array!');
}
param.forEach((element) => {
if (!element.key || !element.name) {
return;
}
// 使用对象存储键值对,方便快速查找
keyObj[element.key] = element;
valueObj[element.value] = element;
nameObj[element.name] = element;
// 动态添加属性
this[element.key] = element;
this[element.name] = element;
if (element.key !== element.value) {
this[element.value] = element;
}
});
// 保存源数组
this.data = param;
this.keyObj = keyObj;
this.valueObj = valueObj;
this.nameObj = nameObj;
// 防止被修改
Object.freeze(this);
}
getColorByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.color;
}
getData() {
return this.data;
}
getKeyByName(name) {
const prop = this.nameOf(name);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.key;
}
getNameByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.name;
}
getNameByValue(value) {
const prop = this.valueOf(value);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.name;
}
getValueByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.value;
}
getValueByName(name) {
const prop = this.nameOf(name);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.value;
}
keyOf(key) {
return this.keyObj[key];
}
nameOf(name) {
return this.nameObj[name];
}
valueOf(value) {
return this.valueObj[value];
}
}
// to 方法,封装try catch
const to = async (
promise,
errorExt,
) => {
try {
const data = await promise
const result = [null, data]
return result
} catch (error) {
if (errorExt) {
const parsedError = Object.assign({}, error, errorExt)
return [parsedError, undefined]
}
return [error, undefined]
}
}
// 状态
const StatusEnum =
new SuperEnum([
{ key: 'PICKED', name: '已选', color: 'warning', value: 1 },
// 增值
{ key: 'UN_PICKED', name: '未选', color: 'success', value: 0 },
])
// 自动与手动
const AutoEnum =
new SuperEnum([
{ key: 'AUTO', name: '自动', color: 'warning', value: 1 },
// 增值
{ key: 'MANUAL', name: '手动', color: 'success', value: 0 },
])
// tab类型
const TabEnum =
new SuperEnum([
{ key: 'SETTING', name: '押金配置', color: 'warning', value: 1 },
{ key: 'ORDER', name: '订单列表', color: 'success', value: 2 },
])
</script>
<script>
const { createApp, defineComponent, reactive, ref, onMounted, h, computed } = Vue;
const { useMessage, useNotification, dateZhCN, NButton, NTag, zhCN, NSpace, NImageGroup, NImage, NText } = naive
// 添加全局 loading 指令
const uploadUrl = window.location.origin + '/api-common/v1/upload?folder=' + 'gk/invoice' + '&cmptId=' + getCId()
const BASE_ECC_URL = "https://ecc.geexek.com"
// 人员列表
const PLAYERS_URL = BASE_ECC_URL + '/api-pick/v1/enroll-page'
const PLAYERS_EXPORT_URL = BASE_ECC_URL + '/api-pick/v1/export-markno-enroll?cmptId=' + getCId()
const PLAYERS_IMPORT_URL = BASE_ECC_URL + '/api-pick/v1/import-mark-enroll'
const PLAYERS_DOWNLOAD_URL = "https://geexekimgoss-sxk.oss-cn-hangzhou.aliyuncs.com/gk/common/markNoSelect/%E9%80%89%E5%8F%B7%E5%90%8D%E5%8D%95%E8%A1%A8%E6%A8%A1%E7%89%88v1.xlsx"
// 号码簿列表
const MARK_NO_URL = BASE_ECC_URL + '/api-pick/v1/mark-list'
const MARK_NO_EXPORT_URL = BASE_ECC_URL + '/api-pick/v1/export-markno-library-v1?cmptId=' + getCId()
const MARK_NO_IMPORT_URL = BASE_ECC_URL + '/api-pick/v1/import-mark-library'
const MARK_NO_DOWNLOAD_URL = "https://geexekimgoss-sxk.oss-cn-hangzhou.aliyuncs.com/gk/common/markNoSelect/%E5%8F%82%E8%B5%9B%E5%8F%B7%E5%85%A5%E5%BA%93%E6%A8%A1%E7%89%88v1.xlsx"
// 随机分配名单
const RANDOM_ASSIGN_URL = BASE_ECC_URL + '/api-pick/v1/pick-up-batch?cmptId=' + getCId()
// TODO 保存押金配置
const SAVE_DEPOSIT_URL = ""
const GET_DEPOSIT_URL = ""
const MyApp = defineComponent({
name: 'MyApp',
template: '#my-app',
setup() {
const message = useMessage()
const notification = useNotification()
const loading = ref(false)
/***----------------选手名单相关 start -----------------*/
const tabIndex = ref(1)
const isSetting = computed(() => tabIndex.value === 1)
const PLAYER = "player"
const MARK_NO = "markNo"
// 状态统计
const OrderStatusOptions = ref([
{
name: '全部',
value: '',
total: 0
},
{
name: '已支付',
value: 1,
total: 0
},
{
name: '待支付',
value: 2,
total: 0
},
{
name: '已退款',
value: 4,
total: 0
},
{
name: '退款中',
value: 4,
total: 0
},
])
const GpsStatusOptions = ref([
{
name: '全部',
value: '',
total: 0
},
{
name: '已绑定',
value: 1,
total: 0
},
{
name: '未绑定',
value: 2,
total: 0
},
{
name: '已发放',
value: 3,
total: 0
},
{
name: '未发放',
value: 4,
total: 0
},
{
name: '已归还',
value: 5,
total: 0
},
{
name: '未归还',
value: 6,
total: 0
}
])
// 勾选的行数据
const checkedRowKeys = ref([])
// 列表配置
const playersColumns = [
{
type: 'selection',
},
{
title: '姓名',
key: 'name',
},
{
title: '参赛号',
key: MARK_NO,
},
{
title: '手机号',
key: 'phone',
},
{
title: '证件号',
key: 'identityNo',
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right',
align: 'center',
render: (row) => {
// 两个按钮,一个 编辑 一个 删除
return h(NSpace, {}, [
h(NButton, {
type: 'error',
size: 'small',
onClick: async () => {
}
}, '发放'),
h(NButton, {
type: 'primary',
size: 'small',
onClick: () => {
}
}, '归还'),
h(NButton, {
type: 'primary',
size: 'small',
onClick: async () => {
}
}, '退款'),
])
}
},
]
// 分页
const pagination = reactive({
pageSize: 20,
page: 1,
pageCount: 0,
showSizePicker: true,
itemCount: 0,
pageSizes: [20, 50, 200, 500],
prefix: (info) => "共" + info.itemCount + "条数据",
onChange: (page) => {
pagination.page = page
fetchData()
},
onUpdatePageSize: (pageSize) => {
pagination.pageSize = pageSize
pagination.page = 1
fetchData()
}
})
// 查询的参数
const query = reactive({
keyword: undefined,
isPickUp: 1,
markNo: undefined,
})
const keyword = ref('')
// 删除自助选号
const showModal = ref(false)
const showBatchModal = ref(false)
// 名单列表
const list = ref([])
/***----------------订单列表 start -----------------*/
function handleCheck(rowKeys) {
checkedRowKeys.value = rowKeys
}
function rowKey(row) {
return row.markNo
}
/***----------------订单列表 end -----------------*/
/***----------------押金配置 start -----------------*/
// ref 押金表单
const depositFormRef = ref()
// 组别选项
const roadOptions = ref([
{
label: '组别一',
value: 'song0',
disabled: true
},
{
label: '组别二',
value: 'song1'
},
{
label: '组别三',
value: 'song2'
},
])
// 押金表单
const depositForm = reactive({
money: 500, // 金额
notice: '', // 公告
content: '', // 富文本
status: 0,// 0 关闭,1开启
})
// 押金配置规则
const depositRules = {
money: [{
required: true,
message: '请输入押金金额',
trigger: ['blur', 'change']
}],
}
// 状态更新
const updateStatus = (value) => {
pagination.page = 1
query.isPickUp = value
fetchData()
}
// 状态tab
const updateTab = (value) => {
tabIndex.value = value
fetchData()
}
// 上传完成
const uploadFinish = ({
file,
event
}) => {
const response = JSON.parse(event?.target?.response)
file.url = response.content
return file
}
// 重置form数据
const resetForm = () => {
Object.assign(formData, {
id: null,
remarks: null,
name: "",
markNo: "",
phone: "",
area: "",
province: "",
city: "",
location: "",
deliveryNo: "",
expressCompany: "",
status: StatusEnum.SUBMIT.value,
remark: ""
})
}
// 获取自助选号分页列表
const fetchData = async () => {
list.value = []
loading.value = true
let params = {
...query,
current: pagination.page,
size: pagination.pageSize,
cmptId: getCId(),
keyword: keyword.value,
}
// 发起请求
const [err, res] = await to(fetch(PLAYERS_URL, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
}).then(res => res.json()))
loading.value = false
if (err) {
message.error(err)
return
}
if (res.code === 200) {
list.value = res.data.records
pagination.itemCount = res.data.total
// // 全部状态下不获取统计
getStats()
} else {
message.error(res.message)
}
}
// 随机分配名单
const randomAssign = async () => {
loading.value = true
const [err, res] = await to(fetch(RANDOM_ASSIGN_URL, {
method: "GET",
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json()))
loading.value = false
if (err) {
message.error(err)
return
}
if (res.code === 200) {
message.success('随机分配成功')
fetchData()
} else {
message.error(res.message)
}
}
// 获取统计 用fetch改造
const getStats = async () => {
loading.value = true
let params = {
page: 1,
size: 1,
cmptId: getCId(),
}
let paramsList = [1, 0].map(v => ({
...params,
isPickUp: v,
}))
const requests = paramsList.map(v => {
// 发起请求
const url = isSetting.value ? PLAYERS_URL : MARK_NO_URL
return fetch(url, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(v)
}).then(res => res.json())
})
const res = await Promise.all(requests)
loading.value = false
if (isSetting.value) {
OrderStatusOptions.value[1].total = res[0].data.total
OrderStatusOptions.value[2].total = res[1].data.total
} else {
OrderStatusOptions.value[1].total = res[0].data.total
OrderStatusOptions.value[2].total = res[1].data.total
}
OrderStatusOptions.value[0].total = OrderStatusOptions.value[2].total + OrderStatusOptions.value[1].total
}
// 名单导出
const exportExcel = (type) => {
const url = type === PLAYER ? PLAYERS_EXPORT_URL : MARK_NO_EXPORT_URL
window.location.href = url
}
// 文件上传成功
const onChange = async (fileEvent, type) => {
loading.value = true
const formData = new FormData();
formData.append('file', fileEvent.file.file);
formData.append('cmptId', getCId());
const url = type === PLAYER ? PLAYERS_IMPORT_URL : MARK_NO_IMPORT_URL
const [err, res] = await to(fetch(url, {
method: "POST",
body: formData
}).then(res => res.json()))
loading.value = false
if (err) {
message.error(err)
return
}
fileList.value = []
if (res.code === 200) {
message.success('导入成功')
fetchData()
} else {
message.error(res.message)
}
}
// 导出
const download = (type) => {
const url = type === PLAYER ? PLAYERS_DOWNLOAD_URL : MARK_NO_DOWNLOAD_URL
window.location.href = url
}
// 富文本编辑器 .setContent() .getContent()
let editor;
/***---------------- 自助选号相关 end -----------------*/
/***---------------- 押金交互相关 start -----------------*/
// todo 保存押金配置
const saveDeposit = () => {
depositFormRef.value?.validate(async (errors) => {
if (errors) return message.error('请检查表单是否填写完整')
loading.value = true
const [err, res] = await to(fetch(SAVE_DEPOSIT_URL, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(depositForm.value)
}).then(res => res.json()))
loading.value = false
if (err) {
message.error(err)
return
}
if (res.code === 200) {
message.success('保存成功')
fetchData()
getDeposit()
} else {
message.error(res.message)
}
})
}
// todo 获取押金配置
const getDeposit = async () => {
loading.value = true
const [err, res] = await to(fetch(GET_DEPOSIT_URL, {
method: "GET",
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json()))
loading.value = false
if (err) {
message.error(err)
return
}
if (res.code === 200) {
depositForm.value = res.data
editor.setContent(res.data.content)
} else {
message.error(res.message)
}
}
// todo 批量退款
const batchRefund = () => { }
// todo 触发批量退款
const onBatchRefund = () => {
showBatchModal.value = true
}
/***---------------- 押金交互相关 end -----------------*/
onMounted(() => {
fetchData()
editor = UE.getEditor('rich-text-container');
})
return {
// 自助选号相关
updateStatus,
updateTab,
list,
query,
pagination,
playersColumns,
isSetting,
showModal,
showBatchModal,
handleCheck,
checkedRowKeys,
// 文件相关
keyword,
fetchData,
tabIndex,
randomAssign,
// 公共数据
loading,
StatusEnum,
TabEnum,
OrderStatusOptions,
GpsStatusOptions,
// 名单导出
exportExcel,
download,
uploadFinish,
onChange,
PLAYER,
MARK_NO,
// 押金相关
depositForm,
depositRules,
roadOptions,
saveDeposit,
getDeposit,
batchRefund,
depositFormRef,
}
}
})
const app = Vue.createApp({
setup() {
return {
locale: zhCN,
dateLocale: dateZhCN
}
}
})
app.component('my-app', MyApp)
app.use(naive)
app.mount('#app')
</script>
</body>
</html>
\ No newline at end of file
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="fns" uri="/WEB-INF/tlds/fns.tld" %>
<%@ taglib prefix="fnc" uri="/WEB-INF/tlds/fnc.tld" %>
<c:set var="ctx" value="${pageContext.request.contextPath}${fns:getAdminPath()}" />
<c:set var="path" value="${pageContext.request.contextPath}${fns:getPath()}" />
<c:set var="jaaproot" value="${pageContext.request.contextPath}" />
<c:set var="ctxStatic" value="${pageContext.request.contextPath}/static" />
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>信息收集</title>
<!-- ... 保留原有的 Vue 引入 ... -->
<script src="${ctxStatic}/vue/vue.global.min.js"></script>
<!-- 添加 Naive UI 的引入 -->
<!-- 会使用最新版本,你最好指定一个版本 -->
<script src="${ctxStatic}/navie-ui/index.min.js"></script>
<!-- <script nomodule
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script> -->
<script type="module"
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
<!-- Favicon-->
<link rel="shortcut icon" href="https://img.geexek.com/files/home/favicon.ico">
<!-- 自定义css -->
<link rel="stylesheet" href="${ctxStatic}/css/base.css">
<!-- Bootstrap CSS-->
<link rel="stylesheet"
href="${ctxStatic}/newdermon/vendor/bootstrap/css/bootstrap.min.css">
<!-- theme stylesheet-->
<link rel="stylesheet"
href="${ctxStatic}/newdermon/css/style.default.css?v=<%=System.currentTimeMillis()%>"
id="theme-stylesheet">
<!-- Custom stylesheet - for your changes-->
<link rel="stylesheet"
href="${ctxStatic}/newdermon/css/custom.css?v=<%=System.currentTimeMillis()%>">
<!-- Favicon-->
<link rel="shortcut icon" href="https://img.geexek.com/files/home/favicon.ico">
<link rel="stylesheet"
href="${ctxStatic}/cmpt/setting.css?v=<%=System.currentTimeMillis()%>">
<script src="${ctxStatic}/newdermon/vendor/jquery/jquery.min.js"></script>
<style>
#app {
padding: 0 20px 20px 20px;
}
.layout {
min-height: 100vh;
}
.carousel-img {
width: 100%;
height: 240px;
object-fit: cover;
}
[v-cloak] {
display: none;
}
.n-message .n-message__icon>* {
position: absolute;
left: 0;
top: -5px !important;
right: 0;
bottom: 0;
}
.n-dialog__icon>* {
position: absolute;
left: 0;
top: -2px !important;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<jsp:include page="../common/header.jsp"></jsp:include>
<div class="page-content d-flex align-items-stretch main-body">
<jsp:include page="../common/menu.jsp"></jsp:include>
<div id="app" class="flex-1" v-cloak>
<n-config-provider :locale="locale" :date-locale="dateLocale">
<n-modal-provider>
<n-message-provider>
<n-notification-provider>
<my-app></my-app>
</n-notification-provider>
</n-message-provider>
</n-modal-provider>
</n-config-provider>
</div>
</div>
<script type="text/x-template" id="my-app">
<n-space vertical >
<n-card :bordered="false">
<div class="w-full flex flex-row items-center gap-x-2">
<div style="min-width: 200px">
<n-tabs type="segment" @update:value="updateTab":value="tabIndex" animated pane-style="padding:0;">
<n-tab-pane v-for="item in TabEnum.data" :name="item.value" :tab="item.name"/>
</n-tabs>
</div>
<div class="ml-auto" v-if="!isSetting">
<n-input v-model:value="keyword" placeholder="请输入关键字进行搜索" :clearable="true" @change="fetchData" />
</div>
</div>
<div class="w-full gap-x-2 flex flex-row items-center mt-3">
<div class="flex-1" v-if="!isSetting" >
</div>
<n-space>
<n-button v-if="checkedRowKeys.length" type="error">
批量操作
</n-button>
</n-space>
</div>
</n-card>
<!-- 信息收集列表 -->
<n-data-table v-if="!isSetting" :columns="playersColumns" :loading="loading" :row-key="rowKey" :data="list" :pagination="pagination"
:bordered="false"
:remote="true"
@update:checked-row-keys="handleCheck"
/>
<!-- 信息收集字段配置 -->
<n-card v-if="isSetting" title="信息收集字段配置" :bordered="false" >
<n-form
ref="configFormRef"
:model="configs"
:rules="configRules"
label-placement="left"
label-width="auto"
>
<n-form-item label="状态">
<n-switch
:checked-value="1"
:unchecked-value="0"
v-model:value="configs.status"
>
<template #checked>
开启
</template>
<template #unchecked>
关闭
</template>
</n-switch>
</n-form-item>
<n-form-item label="审核">
<n-switch
:checked-value="1"
:unchecked-value="0"
v-model:value="configs.audit"
>
<template #checked>
开启
</template>
<template #unchecked>
关闭
</template>
</n-switch>
</n-form-item>
<n-form-item label="描述" >
<n-input type="textarea" v-model:value="configs.description" placeholder="请输入描述信息" />
</n-form-item>
<n-form-item label="开始时间" path="startTime">
<n-date-picker value-format="yyyy-MM-dd HH:mm:ss" date-format="yyyy-MM-dd HH:mm:ss" :value="configs.startTime"
type="datetime"
clearable
@update:formatted-value="updateStartTime" />
</n-form-item>
<n-form-item label="结束时间" path="endTime">
<n-date-picker value-format="yyyy-MM-dd HH:mm:ss" date-format="yyyy-MM-dd HH:mm:ss" :value="configs.endTime"
type="datetime"
clearable
@update:formatted-value="updateEndTime" />
</n-form-item>
</n-form>
</n-card>
<!-- 可配置字段 -->
<n-card title="可配置字段" :bordered="false" v-if="isSetting">
<div class="w-full gap-2 flex flex-row items-center flex-wrap">
<n-tag @click="addField(item.key)" v-for="item in enableFields" :key="item.key" type="primary">{{item.name}}</n-tag>
</div>
</n-card>
<!-- 字段规则 -->
<n-card title="字段规则" :bordered="false" v-if="isSetting">
<n-data-table
size="small"
:columns="fieldColumns"
:loading="loading"
:data="configs.fields"
:pagination="false"
:bordered="true"/>
</n-card>
<!-- 操作按钮 -->
<div class="flex flex-row items-center gap-x-2 w-full" v-if="isSetting">
<n-button :disabled="loading" :loading="loading" :block="true" @click="onSubmit" type="primary">
保存
</n-button>
</div>
</n-space>
</script>
<script>
const cmpt = JSON.parse('${fns:toJson(cmpt)}');
// 枚举class
class SuperEnum {
constructor(param) {
const keyObj = {};
const valueObj = {};
const nameObj = {};
if (!Array.isArray(param)) {
throw new TypeError('param is not an array!');
}
param.forEach((element) => {
if (!element.key || !element.name) {
return;
}
// 使用对象存储键值对,方便快速查找
keyObj[element.key] = element;
valueObj[element.value] = element;
nameObj[element.name] = element;
// 动态添加属性
this[element.key] = element;
this[element.name] = element;
if (element.key !== element.value) {
this[element.value] = element;
}
});
// 保存源数组
this.data = param;
this.keyObj = keyObj;
this.valueObj = valueObj;
this.nameObj = nameObj;
// 防止被修改
Object.freeze(this);
}
getColorByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.color;
}
getData() {
return this.data;
}
getKeyByName(name) {
const prop = this.nameOf(name);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.key;
}
getNameByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.name;
}
getNameByValue(value) {
const prop = this.valueOf(value);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.name;
}
getValueByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.value;
}
getValueByName(name) {
const prop = this.nameOf(name);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.value;
}
keyOf(key) {
return this.keyObj[key];
}
nameOf(name) {
return this.nameObj[name];
}
valueOf(value) {
return this.valueObj[value];
}
}
// to 方法,封装try catch
const to = async (
promise,
errorExt,
) => {
try {
const data = await promise
const result = [null, data]
return result
} catch (error) {
if (errorExt) {
const parsedError = Object.assign({}, error, errorExt)
return [parsedError, undefined]
}
return [error, undefined]
}
}
// tab类型
const TabEnum =
new SuperEnum([
{ key: 'SETTING', name: '字段配置', color: 'warning', value: 1 },
{ key: 'ORDER', name: '信息列表', color: 'success', value: 2 },
])
// 配置固定项
const fieldConfigs = [
{
key: "name",
name: "姓名",
type: "text",
required: true,
disabled: false,
editable: true,
placeholder: "请输入姓名",
},
{
key: "phone",
name: "手机号",
type: "text",
required: true,
disabled: false,
editable: true,
placeholder: "请输入手机号",
},
{
key: "email",
name: "邮箱",
type: "text",
required: true,
disabled: false,
editable: true,
placeholder: "请输入邮箱",
},
{
key: "idCard",
name: "身份证号",
type: "text",
required: true,
disabled: false,
editable: true,
placeholder: "请输入身份证号",
},
{
key: "avatar",
name: "头像",
type: "image",
required: true,
disabled: false,
editable: true,
placeholder: "请上传头像",
},
// 备用项
{
key: "remark1",
name: "自定义一",
type: "image",
required: true,
disabled: false,
editable: true,
placeholder: "请输入自定义描述文字",
},
]
</script>
<script>
const { createApp, defineComponent, reactive, ref, onMounted, h, computed } = Vue;
const { useMessage, useNotification, dateZhCN, NButton, NTag, zhCN, NSpace, NImageGroup, NImage, NInput, NSelect, NCheckbox, NText } = naive
// 添加全局 loading 指令
const uploadUrl = window.location.origin + '/api-common/v1/upload?folder=' + 'gk/invoice' + '&cmptId=' + getCId()
const BASE_ECC_URL = "https://ecc.geexek.com"
// 人员列表
const PLAYERS_URL = BASE_ECC_URL + '/api-pick/v1/enroll-page'
const PLAYERS_EXPORT_URL = BASE_ECC_URL + '/api-pick/v1/export-markno-enroll?cmptId=' + getCId()
const PLAYERS_IMPORT_URL = BASE_ECC_URL + '/api-pick/v1/import-mark-enroll'
const PLAYERS_DOWNLOAD_URL = "https://geexekimgoss-sxk.oss-cn-hangzhou.aliyuncs.com/gk/common/markNoSelect/%E9%80%89%E5%8F%B7%E5%90%8D%E5%8D%95%E8%A1%A8%E6%A8%A1%E7%89%88v1.xlsx"
// 号码簿列表
const MARK_NO_URL = BASE_ECC_URL + '/api-pick/v1/mark-list'
const MARK_NO_EXPORT_URL = BASE_ECC_URL + '/api-pick/v1/export-markno-library-v1?cmptId=' + getCId()
const MARK_NO_IMPORT_URL = BASE_ECC_URL + '/api-pick/v1/import-mark-library'
const MARK_NO_DOWNLOAD_URL = "https://geexekimgoss-sxk.oss-cn-hangzhou.aliyuncs.com/gk/common/markNoSelect/%E5%8F%82%E8%B5%9B%E5%8F%B7%E5%85%A5%E5%BA%93%E6%A8%A1%E7%89%88v1.xlsx"
// 随机分配名单
const RANDOM_ASSIGN_URL = BASE_ECC_URL + '/api-pick/v1/pick-up-batch?cmptId=' + getCId()
// TODO 保存信息收集配置
const SAVE_INFORMATION_URL = ""
const GET_CONFIG_URL = ""
const MyApp = defineComponent({
name: 'MyApp',
template: '#my-app',
setup() {
const message = useMessage()
const notification = useNotification()
const loading = ref(false)
/***----------------选手名单相关 start -----------------*/
const tabIndex = ref(1)
const isSetting = computed(() => tabIndex.value === 1)
const PLAYER = "player"
const MARK_NO = "markNo"
/***---------------- 信息收集字段配置 start -----------------*/
const enableFields = computed(() => fieldConfigs.filter(item => !configs.value.fields.map(v => v.key).includes(item.key)))
// 配置项
const configs = ref({
status: 1, // 是否开启
audit: 0, // 是否审核
cmptId: getCId(),
title: "信息收集的标题",
startTime: undefined,
endTime: undefined,
description: "信息收集的描述",
fields: [
{
key: "name",
name: "姓名",
type: "text",
required: true,
disabled: false,
editable: true,
placeholder: "请输入姓名",
},
],
})
// 字段列
const fieldColumns = [
{
title: '字段名称',
key: 'name',
align: 'center',
render(row, index) {
return h(NInput, {
value: row.name,
disabled: !configs.value.fields[index].key.includes('remark'),
onUpdateValue(v) {
configs.value.fields[index].key = v
}
})
}
},
{
title: '字段名称',
key: 'key',
align: 'center',
render(row, index) {
return h(NInput, {
value: row.key,
disabled: !configs.value.fields[index].key.includes('remark'),
onUpdateValue(v) {
configs.value.fields[index].name = v
}
})
}
},
{
title: '字段描述',
key: 'placeholder',
align: 'center',
render(row, index) {
return h(NInput, {
value: row.placeholder,
onUpdateValue(v) {
configs.value.fields[index].placeholder = v
}
})
}
},
{
title: '类型',
key: 'type', // 文本,日期,文件,图片
align: 'center',
render(row, index) {
return h(NSelect, {
value: row.type,
options: [
{ label: '文本', value: 'text' },
{ label: '日期', value: 'date' },
{ label: '文件', value: 'file' },
{ label: '图片', value: 'image' },
],
onUpdateValue(v) {
configs.value.fields[index].type = v
}
})
}
},
{
title: '是否必填',
key: 'required',
align: 'center',
width: 100,
render(row, index) {
return h(NCheckbox, {
checked: row.required,
onUpdateChecked(v) {
configs.value.fields[index].required = v
}
})
}
},
{
title: '是否禁用',
key: 'disabled',
align: 'center',
width: 100,
render(row, index) {
return h(NCheckbox, {
checked: row.disabled,
onUpdateChecked(v) {
configs.value.fields[index].disabled = v
}
})
}
},
{
title: '是否可编辑',
key: 'editable',
align: 'center',
width: 100,
render(row, index) {
return h(NCheckbox, {
checked: row.editable,
onUpdateChecked(v) {
configs.value.fields[index].editable = v
}
})
}
},
{
title: '操作',
key: 'actions',
width: 50,
align: 'center',
render: (row, index) => {
return h(NButton, {
type: 'error',
size: 'small',
onClick: async () => {
configs.value.fields.splice(index, 1)
}
}, '移除')
}
}
]
// ref 信息收集表单
const configFormRef = ref()
// 信息收集配置规则
const configRules = {
title: [{
required: true,
message: '请输入标题',
trigger: ['blur', 'change']
}],
}
// 勾选的行数据
const checkedRowKeys = ref([])
// 列表配置
const playersColumns = [
{
type: 'selection',
},
{
title: '姓名',
key: 'name',
},
{
title: '参赛号',
key: MARK_NO,
},
{
title: '手机号',
key: 'phone',
},
{
title: '证件号',
key: 'identityNo',
},
{
title: '操作',
key: 'action',
width: 200,
fixed: 'right',
align: 'center',
render: (row) => {
// 两个按钮,一个 编辑 一个 删除
return h(NSpace, {}, [
h(NButton, {
type: 'error',
size: 'small',
onClick: async () => {
}
}, '发放'),
h(NButton, {
type: 'primary',
size: 'small',
onClick: () => {
}
}, '归还'),
h(NButton, {
type: 'primary',
size: 'small',
onClick: async () => {
}
}, '退款'),
])
}
},
]
// 分页
const pagination = reactive({
pageSize: 20,
page: 1,
pageCount: 0,
showSizePicker: true,
itemCount: 0,
pageSizes: [20, 50, 200, 500],
prefix: (info) => "共" + info.itemCount + "条数据",
onChange: (page) => {
pagination.page = page
fetchData()
},
onUpdatePageSize: (pageSize) => {
pagination.pageSize = pageSize
pagination.page = 1
fetchData()
}
})
// 查询的参数
const query = reactive({
keyword: undefined,
isPickUp: 1,
markNo: undefined,
})
const keyword = ref('')
// 名单列表
const list = ref([])
/***----------------信息列表 start -----------------*/
function handleCheck(rowKeys) {
checkedRowKeys.value = rowKeys
}
function rowKey(row) {
return row.markNo
}
/***----------------信息列表 end -----------------*/
/***----------------表单配置 start -----------------*/
// 日期处理
const updateStartTime = (val) => {
configs.value.startTime = val
}
// 日期处理
const updateEndTime = (val) => {
configs.value.endTime = val
}
// 状态tab
const updateTab = (value) => {
tabIndex.value = value
fetchData()
}
// 获取自助选号分页列表
const fetchData = async () => {
list.value = []
loading.value = true
let params = {
...query,
current: pagination.page,
size: pagination.pageSize,
cmptId: getCId(),
keyword: keyword.value,
}
// 发起请求
const [err, res] = await to(fetch(PLAYERS_URL, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
}).then(res => res.json()))
loading.value = false
if (err) {
message.error(err)
return
}
if (res.code === 200) {
list.value = res.data.records
pagination.itemCount = res.data.total
// // 全部状态下不获取统计
} else {
message.error(res.message)
}
}
/***---------------- 自助选号相关 end -----------------*/
/***---------------- 信息收集交互相关 start -----------------*/
// todo 保存信息收集配置
const saveConfig = () => {
configFormRef.value?.validate(async (errors) => {
if (errors) return message.error('请检查表单是否填写完整')
loading.value = true
const [err, res] = await to(fetch(SAVE_INFORMATION_URL, {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(configs.value)
}).then(res => res.json()))
loading.value = false
if (err) {
message.error(err)
return
}
if (res.code === 200) {
message.success('保存成功')
fetchData()
getConfig()
} else {
message.error(res.message)
}
})
}
// todo 获取信息收集配置
const getConfig = async () => {
loading.value = true
const [err, res] = await to(fetch(GET_CONFIG_URL, {
method: "GET",
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.json()))
loading.value = false
if (err) {
message.error(err)
return
}
if (res.code === 200) {
configs.value = res.data
} else {
message.error(res.message)
}
}
// todo 添加字段
const addField = (key) => {
const field = fieldConfigs.find(item => item.key === key)
configs.value.fields.push(field)
}
/***---------------- 信息收集交互相关 end -----------------*/
onMounted(() => {
fetchData()
})
return {
// 自助选号相关
updateTab,
list,
query,
pagination,
playersColumns,
isSetting,
handleCheck,
checkedRowKeys,
// 文件相关
keyword,
fetchData,
tabIndex,
// 公共数据
loading,
TabEnum,
// 名单导出
PLAYER,
MARK_NO,
// 配置相关
configs,
configRules,
saveConfig,
getConfig,
configFormRef,
updateStartTime,
updateEndTime,
fieldColumns,
addField,
fieldConfigs,
enableFields,
}
}
})
const app = Vue.createApp({
setup() {
return {
locale: zhCN,
dateLocale: dateZhCN
}
}
})
app.component('my-app', MyApp)
app.use(naive)
app.mount('#app')
</script>
</body>
</html>
\ No newline at end of file
...@@ -404,6 +404,20 @@ ...@@ -404,6 +404,20 @@
<span class="menu-txt">领物导航</span> <span class="menu-txt">领物导航</span>
</a> </a>
</li> </li>
<li class="gpsDeposit menu-sub-item">
<a
href="/gk/gpsDeposit/index/${cmpt.cmptId}?uId=${uId}&cId=${cId}&timestamp=${timestamp}&sign=${sign}">
<span class="left-arrow"></span>
<span class="menu-txt">GPS押金(ing)</span>
</a>
</li>
<li class="information menu-sub-item">
<a
href="/gk/information/index/${cmpt.cmptId}?uId=${uId}&cId=${cId}&timestamp=${timestamp}&sign=${sign}">
<span class="left-arrow"></span>
<span class="menu-txt">信息收集(ing)</span>
</a>
</li>
<li class="wechat menu-sub-item"> <li class="wechat menu-sub-item">
<a <a
href="/gk/wechatMini/index/${cmpt.cmptId}?uId=${uId}&cId=${cId}&timestamp=${timestamp}&sign=${sign}"> href="/gk/wechatMini/index/${cmpt.cmptId}?uId=${uId}&cId=${cId}&timestamp=${timestamp}&sign=${sign}">
...@@ -473,7 +487,7 @@ ...@@ -473,7 +487,7 @@
var classes = ['cmptSetting', 'nameList', 'manual', 'contact', 'manage', var classes = ['cmptSetting', 'nameList', 'manual', 'contact', 'manage',
'route', 'road', 'equip', 'photo', 'news', 'cert', 'data', 'route', 'road', 'equip', 'photo', 'news', 'cert', 'data',
'banner', 'score', 'auto', 'scan', 'review', 'customer', 'rule', 'banner', 'score', 'auto', 'scan', 'review', 'customer', 'rule',
'outter-link', 'sure-letter', 'rankSetting', 'service', "module", "swiper", "bag", "invoice", "markNoSelect", "feedback", "markNoRule", "gunRule", "disClaimer", "location", "wechat"]; 'outter-link', 'sure-letter', 'rankSetting', 'service', "module", "swiper", "bag", "invoice", "markNoSelect", "feedback", "markNoRule", "gunRule", "disClaimer", "location", "wechat", "gpsDeposit", "information"];
let url = document.location.href; let url = document.location.href;
var done = false; var done = false;
for (var i in classes) { for (var i in classes) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment