feat: 页面开发

This commit is contained in:
张云杰 2025-08-30 20:37:57 +08:00
parent c7b86f489b
commit 39bc91070b
26 changed files with 2439 additions and 148 deletions

View File

@ -0,0 +1,29 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/website/case/${url}`, ...arg)
/**
* 案例控制器
*/
export default {
// 获取分页
page(data) {
return request('page', data, 'get')
},
// 获取列表
list(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
save(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除
deleteData(data) {
return request('delete', data)
},
// 获取详情
detail(data) {
return request('detail', data, 'get')
},
}

View File

@ -0,0 +1,29 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/website/caseType/${url}`, ...arg)
/**
* 案例类型控制器
*/
export default {
// 获取分页
page(data) {
return request('page', data, 'get')
},
// 获取列表
list(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
save(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除
deleteData(data) {
return request('delete', data)
},
// 获取详情
detail(data) {
return request('detail', data, 'get')
},
}

View File

@ -0,0 +1,33 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/website/comment/${url}`, ...arg)
/**
* 客户评价控制器
*/
export default {
// 获取分页
page(data) {
return request('page', data, 'get')
},
// 获取列表
list(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
save(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除
deleteData(data) {
return request('delete', data)
},
// 获取详情
detail(data) {
return request('detail', data, 'get')
},
// 状态修改
changeStatus(data) {
return request('changeStatus', data, 'get')
},
}

View File

@ -0,0 +1,29 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/website/consult/${url}`, ...arg)
/**
* 咨询控制器
*/
export default {
// 获取分页
page(data) {
return request('page', data, 'get')
},
// 获取列表
list(data) {
return request('list', data, 'get')
},
// 处理信息
save(data) {
return request('handle', data)
},
// 删除
deleteData(data) {
return request('delete', data)
},
// 获取详情
detail(data) {
return request('detail', data, 'get')
},
}

View File

@ -0,0 +1,29 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/website/consultType/${url}`, ...arg)
/**
* 咨询类型控制器
*/
export default {
// 获取分页
page(data) {
return request('page', data, 'get')
},
// 获取列表
list(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
save(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除
deleteData(data) {
return request('delete', data)
},
// 获取详情
detail(data) {
return request('detail', data, 'get')
},
}

View File

@ -0,0 +1,29 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/website/product/${url}`, ...arg)
/**
* 硬件产品控制器
*/
export default {
// 获取分页
page(data) {
return request('page', data, 'get')
},
// 获取列表
list(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
save(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除
deleteData(data) {
return request('delete', data)
},
// 获取详情
detail(data) {
return request('detail', data, 'get')
},
}

View File

@ -0,0 +1,29 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/website/software/${url}`, ...arg)
/**
* 解决方案控制器
*/
export default {
// 获取分页
page(data) {
return request('page', data, 'get')
},
// 获取列表
list(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
save(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除
deleteData(data) {
return request('delete', data)
},
// 获取详情
detail(data) {
return request('detail', data, 'get')
},
}

View File

@ -0,0 +1,29 @@
import { baseRequest } from '@/utils/request'
const request = (url, ...arg) => baseRequest(`/website/target/${url}`, ...arg)
/**
* 目标控制器
*/
export default {
// 获取分页
page(data) {
return request('page', data, 'get')
},
// 获取列表
list(data) {
return request('list', data, 'get')
},
// 提交表单 edit为true时为编辑默认为新增
save(data, edit = false) {
return request(edit ? 'edit' : 'add', data)
},
// 删除
deleteData(data) {
return request('delete', data)
},
// 获取详情
detail(data) {
return request('detail', data, 'get')
},
}

View File

@ -53,10 +53,6 @@ export const Sys = {
{value: 1, label: '阿里云'},
{value: 2, label: '腾讯云'}
],
userType: [
{value: 1, label: '教职工'},
{value: 2, label: '学生'}
],
approveStatus: [
{value: 0, label: '待审核', color: 'default', colorCode: '#cccccc'},
{value: 1, label: '已通过', color: 'success', colorCode: '#52c41a'},
@ -68,150 +64,9 @@ export const Sys = {
{value: false, label: '禁用'}
],
status: [
{value: true, label: '是'},
{value: false, label: '否'}
{value: false, label: '未处理', color: 'default', colorCode: '#cccccc'},
{value: true, label: '已处理', color: 'success', colorCode: '#52c41a'},
],
avatarStatus: [
{value: true, label: '有照片'},
{value: false, label: '没有照片'},
],
paymentType: [
{value: 0, label: '全部'},
{value: 1, label: '智慧迎新'},
{value: 2, label: '缴费'},
{value: 3, label: 'IC卡充值'},
],
payOrigin: [
{value: 0, label: '全部'},
{value: 1, label: '微信h5'},
{value: 2, label: '微信小程序'},
{value: 3, label: '非支付场景下h5'},
{value: 4, label: '非支付场景下pc网站'},
],
payMode: [
{value: 0, label: '全部'},
{value: 1, label: '免费'},
{value: 2, label: '余额'},
{value: 3, label: '现金'},
{value: 4, label: '微信支付'},
{value: 5, label: '支付宝'},
{value: 6, label: '长沙银行'},
{value: 20, label: '混合支付'},
{value: 500, label: '未知'},
],
mpType: [
{value: 1, label: '智慧迎新'},
{value: 2, label: '缴费'},
{value: 3, label: '消息推送'},
]
}
export const Info = {
studentParentRelation: [
{value: 1, label: '爸爸'},
{value: 2, label: '妈妈'},
{value: 3, label: '爷爷'},
{value: 4, label: '奶奶'},
{value: 100, label: '其他'}
],
faceGroupType: [{value: 1, label: "白名单"}, {value: 2, label: "黑名单"}],
appType: [{value: 1, label: '系统应用'}],
politicsStatus: [{value: 1, label: '中共党员'}, {value: 2, label: '中共预备党员'}, {value: 3, label: '共青团员'},
{value: 4, label: '民革会员'}, {value: 5, label: '民盟盟员'}, {value: 6, label: '民建会员'}, {
value: 7,
label: '民进会员'
},
{value: 8, label: '农工党党员'}, {value: 9, label: '致公党党员'}, {
value: 10,
label: '九三学社社员'
}, {value: 11, label: '台盟盟员'},
{value: 12, label: '无党派民主人士'}, {value: 13, label: '群众'},],
studentStatus: [{value: 1, label: '在读'}, {value: 2, label: '毕业'}, {value: 3, label: '退学'},
{value: 4, label: '休学'}, {value: 5, label: '停学'}, {value: 6, label: '复学'}, {value: 7, label: '流失'},
{value: 8, label: '结业'}, {value: 9, label: '肄业'}, {value: 10, label: '转学(转出)'}, {
value: 11,
label: '死亡'
},
{value: 12, label: '保留入学资格'}, {value: 13, label: '公派出国'}, {value: 14, label: '开除'}, {
value: 15,
label: '下落不明'
},
{value: 16, label: '保留学籍(参军)'}, {value: 17, label: '未报到'}, {value: 18, label: '其他'},],
deviceFactory: [{value: 1, label: '海清'},{value: 2, label: '优卡特'}],
deviceType: [{value: 1, label: '电子班牌'}, {value: 2, label: '面板机'},{value: 3, label: '食堂消费机'},],
newsStatus: [{value: 1, label: '未发布'}, {value: 2, label: '已发布'}, {value: 3, label: '草稿'},],
icCardType: [{value: 1, label: 'A自由消费卡'}, {value: 2, label: 'B(包餐卡)'}, {value: 3, label: 'C走读包餐'}, {value: 4, label: 'D月卡22次'}]
}
export const Attendance = {
deviceFactory: [{value: 1, label: '海清'}],
deviceType: [
{value: 1, label: '校门'},
{value: 2, label: '宿舍'}
],
recordType: [
{value: 1, label: '面板机'},
{value: 2, label: 'wifi'}
],
ruleType: [
{value: 1, label: '教职工考勤'},
{value: 2, label: '学生考勤'},
{value: 3, label: '学生宿舍考勤'},
]
}
export const Approve = {
approveType: [
{value: 1, label: '全部同意即通过'},
{value: 2, label: '有一人同意即通过'},
],
studentApproveUserType: [
{value: 1, label: '指定人员'},
{value: 2, label: '指定角色'},
],
studentCustomApproveUserType: [
{value: 1, label: '指定人员'},
{value: 2, label: '指定角色'},
{value: 5, label: '申请人自己选择班级'},
],
staffApproveUserType: [
{value: 1, label: '指定人员'},
{value: 3, label: '指定部门'},
],
staffCustomApproveUserType: [
{value: 1, label: '指定人员'},
{value: 2, label: '指定角色'},
{value: 3, label: '指定部门'},
{value: 4, label: '申请人自己选择审核人员'},
],
approveRoleType: [
{value: 1, label: '班主任'},
{value: 2, label: '系主任'},
],
studentApproveRoleType: [
{value: 1, label: '班主任'},
{value: 2, label: '系主任'},
{value: 4, label: '学院院长'},
{value: 5, label: '学院书记'},
],
}
export const Welcome = {
schoolSystem: [
{value: 2, label: '2年制'},
{value: 3, label: '3年制'},
{value: 4, label: '4年制'},
{value: 5, label: '5年制'},
],
enrollType: [
{value: 0, label: '统招'},
{value: 1, label: '单招'},
],
infoFillType: [
{value: 0, label: '基本信息'},
{value: 1, label: '人员信息'},
{value: 2, label: '到校信息'},
]
}
export const genType = {Sys: Sys}

View File

@ -259,7 +259,6 @@ const detail = () => {
},
}
formData.value = Object.assign(baseData, recordData)
console.info("formData.value", formData.value)
handleInfo()
})

View File

@ -0,0 +1,141 @@
<template>
<xn-form-container
:title="formData.id ? '编辑成功案例' : '增加成功案例'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="案例名称:" name="name">
<a-input v-model:value="formData.name" placeholder="请输入案例名称" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="产品图片:" name="pic">
<Upload ref="uploadPicRef" :upload-mode="'pic'"
:upload-number="1" @uploadDone="uploadPicDone"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序:" name="sort">
<a-input-number v-model:value="formData.sort" placeholder="请输入排序"
style="width: 100%;" :min="1" :max="1000"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="案例类型" name="typeId">
<a-select v-model:value="formData.typeId" :options="caseTypeData"
placeholder="请选择案例类型"/>
</a-form-item>
</a-col>
<a-col :span="18">
<a-form-item label="案例描述:" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入案例描述"
:auto-size="{ minRows: 3, maxRows: 4 }"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="caseForm">
import {cloneDeep} from 'lodash-es'
import {required} from '@/utils/formRules'
import caseApi from '@/api/website/caseApi'
import caseTypeApi from '@/api/website/caseTypeApi'
import Upload from "@/components/XnUpload/index.vue";
//
const visible = ref(false)
const emit = defineEmits({successful: null})
const formRef = ref()
const uploadPicRef = ref()
//
const formData = ref({})
const submitLoading = ref(false)
//
const onOpen = (record) => {
visible.value = true
if (record) {
let recordData = cloneDeep(record)
formData.value = Object.assign({}, recordData)
//
initFiles(record)
}
initCaseType()
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
visible.value = false
}
//
const formRules = {
name: [required('请输入案例名称')],
pic: [required("请上传案例图片")],
sort: [required("请输入排序")],
typeId: [required("请选择案例类型")],
remark: [required("请输入案例描述")],
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
caseApi.save(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
const uploadPicDone = (data) => {
if (data.length > 0) {
formData.value.pic = data[0].url
} else {
formData.value.pic = null
}
}
//
const initFiles = (record) => {
let pics = []
if (record.pic) {
pics.push({url: record.pic})
}
nextTick(() => {
uploadPicRef.value.setFileList(pics)
})
}
//
const caseTypeData = ref([])
const initCaseType = () => {
caseTypeApi.list({}).then(res => {
caseTypeData.value = res.map(x => {
return {label: x.name, value: x.id}
})
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,168 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="案例名称" name="name">
<a-input v-model:value="searchFormState.name" placeholder="请输入案例名称"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="案例类型" name="typeId">
<a-select v-model:value="searchFormState.typeId" :options="caseTypeData" placeholder="请选择案例类型"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="table.refresh(true)">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('website:case:add')">
<template #icon>
<plus-outlined/>
</template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('website:case:delete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchInfo"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'pic'">
<a-image v-if="record.pic"
:width="80"
:height="40"
:src="record.pic"
/>
<span v-else></span>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)" v-if="hasPerm('website:case:edit')">编辑</a>
<a-divider type="vertical" v-if="hasPerm(['website:case:edit', 'website:case:delete'], 'and')"/>
<a-popconfirm title="确定要删除吗?" @confirm="deleteInfo(record)">
<a-button type="link" danger size="small" v-if="hasPerm('website:case:delete')">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="table.refresh(true)"/>
</template>
<script setup name="case">
import Form from './form.vue'
import caseApi from '@/api/website/caseApi'
import caseTypeApi from '@/api/website/caseTypeApi'
let searchFormState = reactive({})
const searchFormRef = ref()
const table = ref()
const formRef = ref()
const toolConfig = {refresh: true, height: true, columnSetting: true, striped: false}
const columns = [
{
title: '案例名称',
dataIndex: 'name'
},
{
title: '案例图片',
dataIndex: 'pic'
},
{
title: '案例类型',
dataIndex: 'typeName'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '排序',
dataIndex: 'sort'
},
]
//
if (hasPerm(['website:case:edit', 'website:case:delete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: true,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
return caseApi.page(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
table.value.refresh(true)
}
//
const deleteInfo = (record) => {
let params = [
record.id,
]
caseApi.deleteData(params).then(() => {
table.value.refresh(true)
})
}
//
const deleteBatchInfo = (params) => {
caseApi.deleteData(params).then(() => {
table.value.clearRefreshSelected()
})
}
//
const caseTypeData = ref([])
const initCaseType = () => {
caseTypeApi.list({}).then(res => {
caseTypeData.value = res.map(x => {
return {label: x.name, value: x.id}
})
})
}
initCaseType()
</script>

View File

@ -0,0 +1,83 @@
<template>
<xn-form-container
:title="formData.id ? '编辑案例类型' : '增加案例类型'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="14">
<a-form-item label="类型名称:" name="name">
<a-input v-model:value="formData.name" placeholder="请输入类型名称" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="14">
<a-form-item label="排序:" name="sort">
<a-input-number v-model:value="formData.sort" placeholder="请输入排序"
style="width: 100%;" :min="1" :max="1000"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="caseTypeForm">
import {cloneDeep} from 'lodash-es'
import {required} from '@/utils/formRules'
import caseTypeApi from '@/api/website/caseTypeApi'
//
const visible = ref(false)
const emit = defineEmits({successful: null})
const formRef = ref()
//
const formData = ref({})
const submitLoading = ref(false)
//
const onOpen = (record) => {
visible.value = true
if (record) {
let recordData = cloneDeep(record)
formData.value = Object.assign({}, recordData)
}
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
visible.value = false
}
//
const formRules = {
name: [required('请输入类型名称')],
sort: [required("请输入排序")],
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
caseTypeApi.save(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,142 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="类型名称" name="name">
<a-input v-model:value="searchFormState.name" placeholder="请输入类型名称"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="table.refresh(true)">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('website:caseType:add')">
<template #icon>
<plus-outlined/>
</template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('website:caseType:delete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchInfo"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'pic'">
<a-image v-if="record.pic"
:width="80"
:height="40"
:src="record.pic"
/>
<span v-else></span>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)" v-if="hasPerm('website:caseType:edit')">编辑</a>
<a-divider type="vertical" v-if="hasPerm(['website:caseType:edit', 'website:caseType:delete'], 'and')"/>
<a-popconfirm title="确定要删除吗?" @confirm="deleteInfo(record)">
<a-button type="link" danger size="small" v-if="hasPerm('website:caseType:delete')">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="table.refresh(true)"/>
</template>
<script setup name="caseType">
import Form from './form.vue'
import caseTypeApi from '@/api/website/caseTypeApi'
let searchFormState = reactive({})
const searchFormRef = ref()
const table = ref()
const formRef = ref()
const toolConfig = {refresh: true, height: true, columnSetting: true, striped: false}
const columns = [
{
title: '类型名称',
dataIndex: 'name'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '排序',
dataIndex: 'sort'
},
]
//
if (hasPerm(['website:caseType:edit', 'website:caseType:delete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: true,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
return caseTypeApi.page(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
table.value.refresh(true)
}
//
const deleteInfo = (record) => {
let params = [
record.id,
]
caseTypeApi.deleteData(params).then(() => {
table.value.refresh(true)
})
}
//
const deleteBatchInfo = (params) => {
caseTypeApi.deleteData(params).then(() => {
table.value.clearRefreshSelected()
})
}
</script>

View File

@ -0,0 +1,137 @@
<template>
<xn-form-container
:title="formData.id ? '编辑评价' : '增加评价'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="评价人名称:" name="name">
<a-input v-model:value="formData.name" placeholder="请输入评价人名称" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="评价人头像:" name="pic">
<Upload ref="uploadPicRef" :upload-mode="'pic'"
:upload-number="1" @uploadDone="uploadPicDone"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="评价人描述:" name="remark">
<a-input v-model:value="formData.remark" placeholder="请输入评价人描述" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序:" name="sort">
<a-input-number v-model:value="formData.sort" placeholder="请输入排序"
style="width: 100%;" :min="1" :max="1000"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="评价星际:" name="star">
<a-rate v-model:value="formData.star" allow-half />
</a-form-item>
</a-col>
<a-col :span="18">
<a-form-item label="评价内容:" name="content">
<a-textarea v-model:value="formData.content" placeholder="请输入评价内容"
:auto-size="{ minRows: 3, maxRows: 4 }"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="commentForm">
import {cloneDeep} from 'lodash-es'
import {required} from '@/utils/formRules'
import commentApi from '@/api/website/commentApi'
import Upload from "@/components/XnUpload/index.vue";
//
const visible = ref(false)
const emit = defineEmits({successful: null})
const formRef = ref()
const uploadPicRef = ref()
//
const formData = ref({})
const submitLoading = ref(false)
//
const onOpen = (record) => {
visible.value = true
if (record) {
let recordData = cloneDeep(record)
formData.value = Object.assign({}, recordData)
if (recordData.star) {
formData.value.star = parseFloat(recordData.star)
}
//
initFiles(record)
}
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
visible.value = false
}
//
const formRules = {
name: [required('请输入评价人名称')],
avatar: [required("请上传评价人头像")],
sort: [required("请输入排序")],
remark: [required("请输入评价描述")],
content: [required("请输入评价内容")],
star: [required("请选择评价星级")],
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
commentApi.save(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
const uploadPicDone = (data) => {
if (data.length > 0) {
formData.value.avatar = data[0].url
} else {
formData.value.avatar = null
}
}
//
const initFiles = (record) => {
let pics = []
if (record.avatar) {
pics.push({url: record.avatar})
}
nextTick(() => {
uploadPicRef.value.setFileList(pics)
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,175 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="评价人名称" name="name">
<a-input v-model:value="searchFormState.name" placeholder="请输入评价人名称"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="启用状态" name="enabled">
<a-select v-model:value="searchFormState.enabled" placeholder="请选择启用状态"
:options="Sys.enableStatus" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="table.refresh(true)">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('website:comment:add')">
<template #icon>
<plus-outlined/>
</template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('website:comment:delete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchInfo"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'avatar'">
<a-avatar :src="record.avatar"/>
</template>
<template v-if="column.dataIndex === 'star'">
<a-rate :value="parseFloat(record.star)" allow-half disabled />
</template>
<template v-if="column.dataIndex === 'enabled'">
<a-space>
<a-switch v-model:checked="record.enabled" @change="enabledChange(record.id)"/>
</a-space>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)" v-if="hasPerm('website:comment:edit')">编辑</a>
<a-divider type="vertical" v-if="hasPerm(['website:comment:edit', 'website:comment:delete'], 'and')"/>
<a-popconfirm title="确定要删除吗?" @confirm="deleteInfo(record)">
<a-button type="link" danger size="small" v-if="hasPerm('website:comment:delete')">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="table.refresh(true)"/>
</template>
<script setup name="comment">
import Form from './form.vue'
import commentApi from '@/api/website/commentApi'
import {Sys} from "@/utils/constant";
let searchFormState = reactive({})
const searchFormRef = ref()
const table = ref()
const formRef = ref()
const toolConfig = {refresh: true, height: true, columnSetting: true, striped: false}
const columns = [
{
title: '评价人名称',
dataIndex: 'name'
},
{
title: '评价人头像',
dataIndex: 'avatar'
},
{
title: '评价星级',
dataIndex: 'star'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '排序',
dataIndex: 'sort'
},
{
title: '启用状态',
dataIndex: 'enabled'
},
]
//
if (hasPerm(['website:comment:edit', 'website:comment:delete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: true,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
return commentApi.page(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
table.value.refresh(true)
}
//
const deleteInfo = (record) => {
let params = [
record.id,
]
commentApi.deleteData(params).then(() => {
table.value.refresh(true)
})
}
//
const deleteBatchInfo = (params) => {
commentApi.deleteData(params).then(() => {
table.value.clearRefreshSelected()
})
}
//
const enabledChange = (id) => {
commentApi.changeStatus({id: id}).then(() => {
table.value.refresh(true)
})
}
</script>

View File

@ -0,0 +1,100 @@
<template>
<xn-form-container
title="咨询信息"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-descriptions bordered title="咨询信息" size="small" :column="2" :labelStyle="{'width': '180px'}">
<a-descriptions-item label="姓名">{{ formRecord.name }}</a-descriptions-item>
<a-descriptions-item label="手机号">{{ formRecord.phone }}</a-descriptions-item>
<a-descriptions-item label="邮箱">{{ formRecord.email }}</a-descriptions-item>
<a-descriptions-item label="咨询类型">{{ formRecord.typeName }}</a-descriptions-item>
<a-descriptions-item label="咨询内容" :span="2">{{ formRecord.content }}</a-descriptions-item>
<a-descriptions-item label="处理状态" :span="2">
<a-tag :color="formRecord.contactStatus ? Sys.status[1].color : Sys.status[0].color">
{{ formRecord.contactStatus ? Sys.status[1].label : Sys.status[0].label }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="后续备注" :span="2">{{ formRecord.remark }}</a-descriptions-item>
</a-descriptions>
<a-divider orientation="left" style="margin-top: 15px">咨询信息处理</a-divider>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="14">
<a-form-item label="是否处理:" name="contactStatus">
<a-switch v-model:checked="formData.contactStatus" />
</a-form-item>
</a-col>
<a-col :span="14">
<a-form-item label="后续备注:" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入后续备注"
:auto-size="{ minRows: 2, maxRows: 3 }"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="consultForm">
import {cloneDeep} from 'lodash-es'
import {required} from '@/utils/formRules'
import consultApi from '@/api/website/consultApi'
import {Sys} from "@/utils/constant";
//
const visible = ref(false)
const emit = defineEmits({successful: null})
const formRef = ref()
//
const formData = ref({})
const formRecord = ref({})
const submitLoading = ref(false)
//
const onOpen = (record) => {
visible.value = true
if (record) {
let recordData = cloneDeep(record)
formData.value = Object.assign({}, recordData)
formRecord.value = Object.assign({}, recordData)
}
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
visible.value = false
}
//
const formRules = {
contactStatus: [required('请选择处理状态')],
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
consultApi.save(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,159 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="关键词" name="searchKey">
<a-input v-model:value="searchFormState.searchKey" placeholder="姓名/电话/邮箱"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="咨询类型" name="typeId">
<a-select v-model:value="searchFormState.typeId" :options="consultTypeData" placeholder="请选择咨询类型"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-form-item label="处理状态" name="contactStatus">
<a-select v-model:value="searchFormState.contactStatus" placeholder="请选择处理状态"
:options="Sys.status" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="table.refresh(true)">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'contactStatus'">
<a-tag :color="record.contactStatus ? Sys.status[1].color : Sys.status[0].color">
{{ record.contactStatus ? Sys.status[1].label : Sys.status[0].label }}
</a-tag>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)" v-if="hasPerm('website:consult:save')">处理</a>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Handle ref="formRef" @successful="table.refresh(true)"/>
</template>
<script setup name="consult">
import Handle from './handle.vue'
import consultApi from '@/api/website/consultApi'
import consultTypeApi from '@/api/website/consultTypeApi'
import {Sys} from "@/utils/constant";
let searchFormState = reactive({})
const searchFormRef = ref()
const table = ref()
const formRef = ref()
const toolConfig = {refresh: true, height: true, columnSetting: true, striped: false}
const columns = [
{
title: '姓名',
dataIndex: 'name'
},
{
title: '电话',
dataIndex: 'phone'
},
{
title: '邮箱',
dataIndex: 'email'
},
{
title: '咨询类型',
dataIndex: 'typeName'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '处理状态',
dataIndex: 'contactStatus'
},
]
//
if (hasPerm(['website:case:edit', 'website:case:delete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: true,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
return consultApi.page(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
table.value.refresh(true)
}
//
const deleteInfo = (record) => {
let params = [
record.id,
]
consultApi.deleteData(params).then(() => {
table.value.refresh(true)
})
}
//
const deleteBatchInfo = (params) => {
consultApi.deleteData(params).then(() => {
table.value.clearRefreshSelected()
})
}
//
const consultTypeData = ref([])
const initConsultTypeType = () => {
consultTypeApi.list({}).then(res => {
consultTypeData.value = res.map(x => {
return {label: x.name, value: x.id}
})
})
}
initConsultTypeType()
</script>

View File

@ -0,0 +1,83 @@
<template>
<xn-form-container
:title="formData.id ? '编辑咨询类型' : '增加咨询类型'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="14">
<a-form-item label="类型名称:" name="name">
<a-input v-model:value="formData.name" placeholder="请输入类型名称" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="14">
<a-form-item label="排序:" name="sort">
<a-input-number v-model:value="formData.sort" placeholder="请输入排序"
style="width: 100%;" :min="1" :max="1000"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="consultTypeForm">
import {cloneDeep} from 'lodash-es'
import {required} from '@/utils/formRules'
import consultTypeApi from '@/api/website/consultTypeApi'
//
const visible = ref(false)
const emit = defineEmits({successful: null})
const formRef = ref()
//
const formData = ref({})
const submitLoading = ref(false)
//
const onOpen = (record) => {
visible.value = true
if (record) {
let recordData = cloneDeep(record)
formData.value = Object.assign({}, recordData)
}
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
visible.value = false
}
//
const formRules = {
name: [required('请输入类型名称')],
sort: [required("请输入排序")],
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
consultTypeApi.save(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,142 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="类型名称" name="name">
<a-input v-model:value="searchFormState.name" placeholder="请输入类型名称"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="table.refresh(true)">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('website:consultType:add')">
<template #icon>
<plus-outlined/>
</template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('website:consultType:delete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchInfo"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'pic'">
<a-image v-if="record.pic"
:width="80"
:height="40"
:src="record.pic"
/>
<span v-else></span>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)" v-if="hasPerm('website:consultType:edit')">编辑</a>
<a-divider type="vertical" v-if="hasPerm(['website:consultType:edit', 'website:consultType:delete'], 'and')"/>
<a-popconfirm title="确定要删除吗?" @confirm="deleteInfo(record)">
<a-button type="link" danger size="small" v-if="hasPerm('website:consultType:delete')">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="table.refresh(true)"/>
</template>
<script setup name="consultType">
import Form from './form.vue'
import consultTypeApi from '@/api/website/consultTypeApi'
let searchFormState = reactive({})
const searchFormRef = ref()
const table = ref()
const formRef = ref()
const toolConfig = {refresh: true, height: true, columnSetting: true, striped: false}
const columns = [
{
title: '类型名称',
dataIndex: 'name'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '排序',
dataIndex: 'sort'
},
]
//
if (hasPerm(['website:consultType:edit', 'website:consultType:delete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: true,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
return consultTypeApi.page(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
table.value.refresh(true)
}
//
const deleteInfo = (record) => {
let params = [
record.id,
]
consultTypeApi.deleteData(params).then(() => {
table.value.refresh(true)
})
}
//
const deleteBatchInfo = (params) => {
consultTypeApi.deleteData(params).then(() => {
table.value.clearRefreshSelected()
})
}
</script>

View File

@ -0,0 +1,187 @@
<template>
<xn-form-container
:title="formData.id ? '编辑产品' : '增加产品'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="产品名称:" name="name">
<a-input v-model:value="formData.name" placeholder="请输入产品名称" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="产品图片:" name="pic">
<Upload ref="uploadPicRef" :upload-mode="'pic'"
:upload-number="1" @uploadDone="uploadPicDone"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序:" name="sort">
<a-input-number v-model:value="formData.sort" placeholder="请输入排序"
style="width: 100%;" :min="1" :max="1000"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="产品标签:" name="tag">
<a-input v-model:value="formData.tag" placeholder="请输入产品标签" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="18">
<a-form-item label="产品描述:" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入产品描述"
:auto-size="{ minRows: 3, maxRows: 4 }"/>
</a-form-item>
</a-col>
<a-col :span="24">
<a-button @click="addTab">新增优势</a-button>
<a-tabs v-model:activeKey="activeIndex" hide-add type="editable-card"
@edit="deleteTab" style="margin-top: 15px">
<a-tab-pane v-for="(item, index) in formData.extra.advantages"
:key="'advantage' + (index + 1)" :tab="'优势' + (index + 1)" closable>
<a-row>
<a-col :span="12">
<a-form-item label="名称:" :name="['extra', 'advantages', index, 'name']">
<a-input v-model:value="item.name" autocomplete="off"
show-count :maxlength="20" placeholder="请输入名称"/>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="productForm">
import {cloneDeep} from 'lodash-es'
import {required} from '@/utils/formRules'
import productApi from '@/api/website/productApi'
import Upload from "@/components/XnUpload/index.vue";
//
const visible = ref(false)
const emit = defineEmits({successful: null})
const formRef = ref()
const uploadPicRef = ref()
//
const formData = ref({
extra: {
advantages: []
}
})
const submitLoading = ref(false)
//
const onOpen = (record) => {
visible.value = true
if (record) {
let recordData = cloneDeep(record)
formData.value = Object.assign({
extra: {
advantages: []
}
}, recordData)
//
initFiles(record)
}
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {
extra: {
advantages: []
}
}
visible.value = false
// tab
activeIndex.value = "advantage1"
}
//
const formRules = {
name: [required('请输入产品名称')],
pic: [required("请上传产品图片")],
sort: [required("请输入排序")],
remark: [required("请输入产品描述")],
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
productApi.save(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
const uploadPicDone = (data) => {
if (data.length > 0) {
formData.value.pic = data[0].url
} else {
formData.value.pic = null
}
}
//
const initFiles = (record) => {
let pics = []
if (record.pic) {
pics.push({url: record.pic})
}
nextTick(() => {
uploadPicRef.value.setFileList(pics)
})
}
// tab
const activeIndex = ref('advantage1')
// tab
const addTab = () => {
let items = formData.value.extra.advantages
let singleNode = {
advantage: ''
}
items.push(singleNode)
formData.value.extra.advantages = [...items]
activeIndex.value = 'advantage' + items.length
}
// tab
const deleteTab = (targetName) => {
let removeIndex
let items = formData.value.extra.advantages
items.forEach((node, index) => {
if ('advantage' + (index + 1) === targetName) {
removeIndex = index
}
})
//
items.splice(removeIndex, 1)
// tab
activeIndex.value = "advantage1"
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,150 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="产品名称" name="name">
<a-input v-model:value="searchFormState.name" placeholder="请输入产品名称"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="table.refresh(true)">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('website:product:add')">
<template #icon>
<plus-outlined/>
</template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('website:product:delete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchInfo"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'pic'">
<a-image v-if="record.pic"
:width="80"
:height="40"
:src="record.pic"
/>
<span v-else></span>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)" v-if="hasPerm('website:product:edit')">编辑</a>
<a-divider type="vertical" v-if="hasPerm(['website:product:edit', 'website:product:delete'], 'and')"/>
<a-popconfirm title="确定要删除吗?" @confirm="deleteInfo(record)">
<a-button type="link" danger size="small" v-if="hasPerm('website:product:delete')">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="table.refresh(true)"/>
</template>
<script setup name="product">
import Form from './form.vue'
import productApi from '@/api/website/productApi'
let searchFormState = reactive({})
const searchFormRef = ref()
const table = ref()
const formRef = ref()
const toolConfig = {refresh: true, height: true, columnSetting: true, striped: false}
const columns = [
{
title: '产品名称',
dataIndex: 'name'
},
{
title: '产品图片',
dataIndex: 'pic'
},
{
title: '产品标签',
dataIndex: 'tag'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '排序',
dataIndex: 'sort'
},
]
//
if (hasPerm(['website:product:edit', 'website:product:delete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: true,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
return productApi.page(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
table.value.refresh(true)
}
//
const deleteInfo = (record) => {
let params = [
record.id,
]
productApi.deleteData(params).then(() => {
table.value.refresh(true)
})
}
//
const deleteBatchInfo = (params) => {
productApi.deleteData(params).then(() => {
table.value.clearRefreshSelected()
})
}
</script>

View File

@ -0,0 +1,121 @@
<template>
<xn-form-container
:title="formData.id ? '编辑方案' : '增加方案'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="方案名称:" name="name">
<a-input v-model:value="formData.name" placeholder="请输入方案名称" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="方案图片:" name="pic">
<Upload ref="uploadPicRef" :upload-mode="'pic'"
:upload-number="1" @uploadDone="uploadPicDone"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序:" name="sort">
<a-input-number v-model:value="formData.sort" placeholder="请输入排序"
style="width: 100%;" :min="1" :max="1000"/>
</a-form-item>
</a-col>
<a-col :span="18">
<a-form-item label="方案描述:" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入方案描述"
:auto-size="{ minRows: 3, maxRows: 4 }"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="softwareForm">
import {cloneDeep} from 'lodash-es'
import {required} from '@/utils/formRules'
import softwareApi from '@/api/website/softwareApi'
import Upload from "@/components/XnUpload/index.vue";
//
const visible = ref(false)
const emit = defineEmits({successful: null})
const formRef = ref()
const uploadPicRef = ref()
//
const formData = ref({})
const submitLoading = ref(false)
//
const onOpen = (record) => {
visible.value = true
if (record) {
let recordData = cloneDeep(record)
formData.value = Object.assign({}, recordData)
//
initFiles(record)
}
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
visible.value = false
}
//
const formRules = {
name: [required('请输入方案名称')],
pic: [required("请上传方案图片")],
sort: [required("请输入排序")],
remark: [required("请输入方案描述")],
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
softwareApi.save(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
const uploadPicDone = (data) => {
if (data.length > 0) {
formData.value.pic = data[0].url
} else {
formData.value.pic = null
}
}
//
const initFiles = (record) => {
let pics = []
if (record.pic) {
pics.push({url: record.pic})
}
nextTick(() => {
uploadPicRef.value.setFileList(pics)
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,146 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="方案名称" name="name">
<a-input v-model:value="searchFormState.name" placeholder="请输入方案名称"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="table.refresh(true)">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('website:software:add')">
<template #icon>
<plus-outlined/>
</template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('website:software:delete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchInfo"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'pic'">
<a-image v-if="record.pic"
:width="80"
:height="40"
:src="record.pic"
/>
<span v-else></span>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)" v-if="hasPerm('website:software:edit')">编辑</a>
<a-divider type="vertical" v-if="hasPerm(['website:software:edit', 'website:software:delete'], 'and')"/>
<a-popconfirm title="确定要删除吗?" @confirm="deleteInfo(record)">
<a-button type="link" danger size="small" v-if="hasPerm('website:software:delete')">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="table.refresh(true)"/>
</template>
<script setup name="software">
import Form from './form.vue'
import softwareApi from '@/api/website/softwareApi'
let searchFormState = reactive({})
const searchFormRef = ref()
const table = ref()
const formRef = ref()
const toolConfig = {refresh: true, height: true, columnSetting: true, striped: false}
const columns = [
{
title: '方案名称',
dataIndex: 'name'
},
{
title: '方案图片',
dataIndex: 'pic'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '排序',
dataIndex: 'sort'
},
]
//
if (hasPerm(['website:software:edit', 'website:software:delete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: true,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
return softwareApi.page(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
table.value.refresh(true)
}
//
const deleteInfo = (record) => {
let params = [
record.id,
]
softwareApi.deleteData(params).then(() => {
table.value.refresh(true)
})
}
//
const deleteBatchInfo = (params) => {
softwareApi.deleteData(params).then(() => {
table.value.clearRefreshSelected()
})
}
</script>

View File

@ -0,0 +1,121 @@
<template>
<xn-form-container
:title="formData.id ? '编辑目标' : '增加目标'"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<a-form ref="formRef" :model="formData" :rules="formRules" layout="vertical">
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="目标名称:" name="name">
<a-input v-model:value="formData.name" placeholder="请输入目标名称" allow-clear/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="目标图片:" name="pic">
<Upload ref="uploadPicRef" :upload-mode="'pic'"
:upload-number="1" @uploadDone="uploadPicDone"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="排序:" name="sort">
<a-input-number v-model:value="formData.sort" placeholder="请输入排序"
style="width: 100%;" :min="1" :max="1000"/>
</a-form-item>
</a-col>
<a-col :span="18">
<a-form-item label="目标描述:" name="remark">
<a-textarea v-model:value="formData.remark" placeholder="请输入目标描述"
:auto-size="{ minRows: 3, maxRows: 4 }"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
<template #footer>
<a-button style="margin-right: 8px" @click="onClose">关闭</a-button>
<a-button type="primary" @click="onSubmit" :loading="submitLoading">保存</a-button>
</template>
</xn-form-container>
</template>
<script setup name="targetForm">
import {cloneDeep} from 'lodash-es'
import {required} from '@/utils/formRules'
import targetApi from '@/api/website/targetApi'
import Upload from "@/components/XnUpload/index.vue";
//
const visible = ref(false)
const emit = defineEmits({successful: null})
const formRef = ref()
const uploadPicRef = ref()
//
const formData = ref({})
const submitLoading = ref(false)
//
const onOpen = (record) => {
visible.value = true
if (record) {
let recordData = cloneDeep(record)
formData.value = Object.assign({}, recordData)
//
initFiles(record)
}
}
//
const onClose = () => {
formRef.value.resetFields()
formData.value = {}
visible.value = false
}
//
const formRules = {
name: [required('请输入目标名称')],
pic: [required("请上传目标图片")],
sort: [required("请输入排序")],
remark: [required("请输入目标描述")],
}
//
const onSubmit = () => {
formRef.value.validate().then(() => {
submitLoading.value = true
const formDataParam = cloneDeep(formData.value)
targetApi.save(formDataParam, formDataParam.id)
.then(() => {
onClose()
emit('successful')
})
.finally(() => {
submitLoading.value = false
})
})
}
//
const uploadPicDone = (data) => {
if (data.length > 0) {
formData.value.pic = data[0].url
} else {
formData.value.pic = null
}
}
//
const initFiles = (record) => {
let pics = []
if (record.pic) {
pics.push({url: record.pic})
}
nextTick(() => {
uploadPicRef.value.setFileList(pics)
})
}
//
defineExpose({
onOpen
})
</script>

View File

@ -0,0 +1,146 @@
<template>
<a-card :bordered="false">
<a-form ref="searchFormRef" name="advanced_search" :model="searchFormState" class="ant-advanced-search-form">
<a-row :gutter="24">
<a-col :span="6">
<a-form-item label="目标名称" name="name">
<a-input v-model:value="searchFormState.name" placeholder="请输入目标名称"/>
</a-form-item>
</a-col>
<a-col :span="6">
<a-button type="primary" @click="table.refresh(true)">查询</a-button>
<a-button style="margin: 0 8px" @click="reset">重置</a-button>
</a-col>
</a-row>
</a-form>
<s-table
ref="table"
:columns="columns"
:data="loadData"
:alert="options.alert.show"
bordered
:row-key="(record) => record.id"
:tool-config="toolConfig"
:row-selection="options.rowSelection"
>
<template #operator class="table-operator">
<a-space>
<a-button type="primary" @click="formRef.onOpen()" v-if="hasPerm('website:target:add')">
<template #icon>
<plus-outlined/>
</template>
新增
</a-button>
<xn-batch-delete
v-if="hasPerm('website:target:delete')"
:selectedRowKeys="selectedRowKeys"
@batchDelete="deleteBatchInfo"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'pic'">
<a-image v-if="record.pic"
:width="80"
:height="40"
:src="record.pic"
/>
<span v-else></span>
</template>
<template v-if="column.dataIndex === 'action'">
<a-space>
<a @click="formRef.onOpen(record)" v-if="hasPerm('website:target:edit')">编辑</a>
<a-divider type="vertical" v-if="hasPerm(['website:target:edit', 'website:target:delete'], 'and')"/>
<a-popconfirm title="确定要删除吗?" @confirm="deleteInfo(record)">
<a-button type="link" danger size="small" v-if="hasPerm('website:target:delete')">删除</a-button>
</a-popconfirm>
</a-space>
</template>
</template>
</s-table>
</a-card>
<Form ref="formRef" @successful="table.refresh(true)"/>
</template>
<script setup name="target">
import Form from './form.vue'
import targetApi from '@/api/website/targetApi'
let searchFormState = reactive({})
const searchFormRef = ref()
const table = ref()
const formRef = ref()
const toolConfig = {refresh: true, height: true, columnSetting: true, striped: false}
const columns = [
{
title: '目标名称',
dataIndex: 'name'
},
{
title: '目标图片',
dataIndex: 'pic'
},
{
title: '创建时间',
dataIndex: 'createdAt'
},
{
title: '排序',
dataIndex: 'sort'
},
]
//
if (hasPerm(['website:target:edit', 'website:target:delete'])) {
columns.push({
title: '操作',
dataIndex: 'action',
align: 'center',
width: '150px'
})
}
const selectedRowKeys = ref([])
//
const options = {
// columns needTotal: true
alert: {
show: true,
clear: () => {
selectedRowKeys.value = ref([])
}
},
rowSelection: {
onChange: (selectedRowKey, selectedRows) => {
selectedRowKeys.value = selectedRowKey
}
}
}
const loadData = (parameter) => {
const searchFormParam = JSON.parse(JSON.stringify(searchFormState))
return targetApi.page(Object.assign(parameter, searchFormParam)).then((data) => {
return data
})
}
//
const reset = () => {
searchFormRef.value.resetFields()
table.value.refresh(true)
}
//
const deleteInfo = (record) => {
let params = [
record.id,
]
targetApi.deleteData(params).then(() => {
table.value.refresh(true)
})
}
//
const deleteBatchInfo = (params) => {
targetApi.deleteData(params).then(() => {
table.value.clearRefreshSelected()
})
}
</script>