im 管理后台
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="关于">
|
||||
{{ name }} 是一个基于 goframe2,vue3,vite2,TypeScript,uniapp
|
||||
的中后台解决方案,它可以帮助你快速搭建企业级中后台项目,相信不管是从新技术使用还是其他方面,都能帮助到你,持续更新中。
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card
|
||||
:bordered="false"
|
||||
title="项目信息"
|
||||
class="mt-4 proCard"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-descriptions bordered label-placement="left" class="py-2">
|
||||
<n-descriptions-item label="HotGo版本">
|
||||
<n-tag type="info"> {{ config?.version }}</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="最后编译时间">
|
||||
<n-tag type="info"> {{ lastBuildTime }} </n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="文档地址">
|
||||
<div class="flex items-center">
|
||||
<a
|
||||
href="https://github.com/bufanyun/hotgo/tree/v2.0/docs/guide-zh-CN"
|
||||
class="py-2"
|
||||
target="_blank"
|
||||
>查看文档地址</a
|
||||
>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="预览地址">
|
||||
<div class="flex items-center">
|
||||
<a href="https://hotgo.facms.cn/admin" class="py-2" target="_blank">查看预览地址</a>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="Github">
|
||||
<div class="flex items-center">
|
||||
<a href="https://github.com/bufanyun/hotgo" class="py-2" target="_blank"
|
||||
>查看Github地址</a
|
||||
>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="QQ交流群">
|
||||
<div class="flex items-center">
|
||||
<a href="https://jq.qq.com/?_wv=1027&k=izJg29Cx" class="py-2" target="_blank"
|
||||
>点击链接加入群聊【HotGo 交流群】</a
|
||||
>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
title="开发环境依赖"
|
||||
class="mt-4 proCard"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-descriptions bordered label-placement="left" class="py-2">
|
||||
<n-descriptions-item v-for="item in devSchema" :key="item.field" :label="item.field">
|
||||
{{ item.label }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
title="生产环境依赖"
|
||||
class="mt-4 proCard"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-descriptions bordered label-placement="left" class="py-2">
|
||||
<n-descriptions-item v-for="item in schema" :key="item.field" :label="item.field">
|
||||
{{ item.label }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
const config = ref(useUserStore.config);
|
||||
export interface schemaItem {
|
||||
field: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const { pkg, lastBuildTime } = __APP_INFO__;
|
||||
const { dependencies, devDependencies, name } = pkg;
|
||||
|
||||
const schema: schemaItem[] = [];
|
||||
const devSchema: schemaItem[] = [];
|
||||
|
||||
Object.keys(dependencies).forEach((key) => {
|
||||
schema.push({ field: key, label: dependencies[key] });
|
||||
});
|
||||
|
||||
Object.keys(devDependencies).forEach((key) => {
|
||||
devSchema.push({ field: key, label: devDependencies[key] });
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="日历"> 一个普通的日历 </n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="mt-4 proCard">
|
||||
<n-calendar
|
||||
v-model:value="value"
|
||||
#="{ year, month, date }"
|
||||
:is-date-disabled="isDateDisabled"
|
||||
@update:value="handleUpdateValue"
|
||||
>
|
||||
{{ year }}-{{ month }}-{{ date }}
|
||||
</n-calendar>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { isYesterday, addDays } from 'date-fns';
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const message = useMessage();
|
||||
return {
|
||||
value: ref(addDays(Date.now(), 1).valueOf()),
|
||||
handleUpdateValue(
|
||||
_: number,
|
||||
{ year, month, date }: { year: number; month: number; date: number }
|
||||
) {
|
||||
message.success(`${year}-${month}-${date}`);
|
||||
},
|
||||
isDateDisabled(timestamp: number) {
|
||||
if (isYesterday(timestamp)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<n-card>
|
||||
<template #header>基础用法</template>
|
||||
<n-descriptions label-placement="top" title="描述">
|
||||
<n-descriptions-item>
|
||||
<template #label> 早餐 </template>
|
||||
苹果
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="早午餐"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="午餐"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="晚餐" :span="2">
|
||||
两个<br />
|
||||
苹果
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="夜宵"> 苹果 </n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card>
|
||||
<template #header>每个描述项都可以设定跨度</template>
|
||||
<n-descriptions label-placement="top" bordered :column="6">
|
||||
<n-descriptions-item>
|
||||
<template #label> 早餐 </template>
|
||||
苹果
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="早午餐"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="午餐"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="晚餐"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="正餐"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="夜宵"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="苹果"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="苹果" :span="2"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="苹果" :span="3"> 苹果 </n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card>
|
||||
<template #header>边框</template>
|
||||
<template #header-extra> 如果有很多多行的信息,你可以把它设为 bordered </template>
|
||||
<n-descriptions bordered>
|
||||
<n-descriptions-item>
|
||||
<template #label> 早餐 </template>
|
||||
苹果
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="午餐"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="晚餐"> 苹果 </n-descriptions-item>
|
||||
<n-descriptions-item label="为什么长">
|
||||
为什么<br />长<br />长<br />长<br />长<br />长
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="为什么长">
|
||||
为什么<br />长<br />长<br />长<br />长<br />长
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="为什么长">
|
||||
为什么<br />长<br />长<br />长<br />长<br />长
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
<n-card>
|
||||
<n-list hoverable clickable>
|
||||
<n-list-item>
|
||||
<n-thing title="相见恨晚" content-style="margin-top: 10px;">
|
||||
<template #description>
|
||||
<n-space size="small" style="margin-top: 4px">
|
||||
<n-tag :bordered="false" type="info" size="small"> 暑夜 </n-tag>
|
||||
<n-tag :bordered="false" type="info" size="small"> 晚春 </n-tag>
|
||||
</n-space>
|
||||
</template>
|
||||
奋勇呀然后休息呀<br />
|
||||
完成你伟大的人生
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<n-thing title="他在时间门外" content-style="margin-top: 10px;">
|
||||
<template #description>
|
||||
<n-space size="small" style="margin-top: 4px">
|
||||
<n-tag :bordered="false" type="info" size="small"> 环形公路 </n-tag>
|
||||
<n-tag :bordered="false" type="info" size="small"> 潜水艇司机 </n-tag>
|
||||
</n-space>
|
||||
</template>
|
||||
最新的打印机<br />
|
||||
复制着彩色傀儡<br />
|
||||
早上好<br />
|
||||
让他带你去被工厂敲击
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</template>
|
||||
<script setup></script>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="card content-box">
|
||||
<n-card :bordered="false" title="复制指令">
|
||||
<n-space>
|
||||
<n-input placeholder="输入内容试试" v-model:value="data" style="width: 350px" />
|
||||
<n-button v-copy="data" type="primary" @click="a">复 制</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
<n-card :bordered="false" title="防抖指令" class="mt-3">
|
||||
<n-button type="primary" v-debounce="b">防抖测试</n-button>
|
||||
</n-card>
|
||||
<n-card :bordered="false" title="节流指令" class="mt-3">
|
||||
<n-button type="primary" v-throttle="c">节流测试</n-button>
|
||||
</n-card>
|
||||
|
||||
<n-card :bordered="false" title="拖拽指令" class="mt-3"> 鼠标放到矩形上面拖拽试试 </n-card>
|
||||
<div class="box" v-draggable> </div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="copyDirect">
|
||||
import { ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
const data = ref<string>();
|
||||
const message = useMessage();
|
||||
const a = () => {
|
||||
message.success('复制成功:' + data.value);
|
||||
};
|
||||
const b = () => {
|
||||
message.success('防抖');
|
||||
console.log(data.value);
|
||||
};
|
||||
const c = () => {
|
||||
message.success('节流');
|
||||
console.log(data.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
body {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #ccc;
|
||||
position: relative;
|
||||
}
|
||||
.content-box {
|
||||
height: 100vh;
|
||||
.box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: #2d8cf0;
|
||||
position: absolute;
|
||||
z-index: 10000000;
|
||||
border-radius: 10px;
|
||||
margin: 20px 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="拖拽"> 常用于卡片,事项,预约,流程,计划等, </n-card>
|
||||
</div>
|
||||
|
||||
<n-alert title="花式拖拽演示" type="info" class="mt-4">
|
||||
每个卡片,都可以上下拖拽顺序,另外不同卡片,也可以拖拽过去,拖拽过来,都不在话下呢,快试试O(∩_∩)O哈哈~
|
||||
</n-alert>
|
||||
|
||||
<n-grid
|
||||
cols="1 s:2 m:3 l:4 xl:4 2xl:4"
|
||||
class="mt-4 proCard"
|
||||
responsive="screen"
|
||||
:x-gap="12"
|
||||
:y-gap="8"
|
||||
>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="需求池"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="info">月</n-tag>
|
||||
</template>
|
||||
|
||||
<Draggable
|
||||
class="draggable-ul"
|
||||
animation="300"
|
||||
:list="demandList"
|
||||
group="people"
|
||||
itemKey="name"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="cursor-move draggable-li">
|
||||
<n-tag type="info">需求</n-tag><span class="ml-2">{{ element.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="开发中"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="info">月</n-tag>
|
||||
</template>
|
||||
|
||||
<Draggable
|
||||
class="draggable-ul"
|
||||
animation="300"
|
||||
:list="exploitList"
|
||||
group="people"
|
||||
itemKey="name"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="cursor-move draggable-li">
|
||||
<n-tag type="warning">开发中</n-tag><span class="ml-2">{{ element.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="已完成"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="info">月</n-tag>
|
||||
</template>
|
||||
<Draggable
|
||||
class="draggable-ul"
|
||||
animation="300"
|
||||
:list="completeList"
|
||||
group="people"
|
||||
itemKey="name"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="cursor-move draggable-li">
|
||||
<n-tag type="error">已完成</n-tag><span class="ml-2">{{ element.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
title="已验收"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="info">月</n-tag>
|
||||
</template>
|
||||
<Draggable
|
||||
class="draggable-ul"
|
||||
animation="300"
|
||||
:list="approvedList"
|
||||
group="people"
|
||||
itemKey="name"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="cursor-move draggable-li">
|
||||
<n-tag type="success">已验收</n-tag><span class="ml-2">{{ element.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
|
||||
const demandList = reactive([
|
||||
{ name: '预约表单页面,能填写预约相关信息', id: 1 },
|
||||
{ name: '促销活动页面,包含促销广告展示', id: 2 },
|
||||
{ name: '商品列表,需要一个到货提醒功能', id: 3 },
|
||||
{ name: '商品需要一个评价功能', id: 4 },
|
||||
{ name: '商品图片需要提供放大镜', id: 5 },
|
||||
{ name: '订单需要提供删除到回收站', id: 6 },
|
||||
{ name: '用户头像上传,需要支持裁剪', id: 7 },
|
||||
{ name: '据说Vue3.2发布了,setup啥时候支持?', id: 8 },
|
||||
]);
|
||||
|
||||
const exploitList = reactive([{ name: '商品图片需要提供放大镜', id: 5 }]);
|
||||
|
||||
const completeList = reactive([{ name: '商品图片需要提供放大镜', id: 5 }]);
|
||||
|
||||
const approvedList = reactive([{ name: '商品图片需要提供放大镜', id: 5 }]);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.draggable-ul {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin-top: -16px;
|
||||
|
||||
.draggable-li {
|
||||
width: 100%;
|
||||
padding: 16px 10px;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #efeff5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<n-card :segmented="{ content: true, footer: true }" footer-style="padding:10px">
|
||||
<template #header>
|
||||
通过设备浏览器信息获取浏览器指纹的插件(官方宣称其识别精度达到99.5%)
|
||||
</template>
|
||||
<div>
|
||||
指纹ID:
|
||||
<n-text type="info">
|
||||
{{ compData.murmur }}
|
||||
</n-text>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import Fingerprint2 from 'fingerprintjs2';
|
||||
|
||||
const compData = reactive({
|
||||
values: {},
|
||||
murmur: '',
|
||||
});
|
||||
|
||||
const createFingerprint = () => {
|
||||
Fingerprint2.get((components) => {
|
||||
compData.values = components.map((component) => component.value); // 配置的值的数组
|
||||
compData.murmur = Fingerprint2.x64hash128(compData.values.join(''), 31).toUpperCase(); // 生成浏览器指纹
|
||||
});
|
||||
};
|
||||
if (window.requestIdleCallback) {
|
||||
requestIdleCallback(() => {
|
||||
createFingerprint();
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
createFingerprint();
|
||||
}, 600);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="基础表单"> 基础表单,用于向用户收集表单信息 </n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="mt-4 proCard">
|
||||
<div class="BasicForm">
|
||||
<BasicForm
|
||||
submitButtonText="提交预约"
|
||||
layout="horizontal"
|
||||
:gridProps="{ cols: 1 }"
|
||||
:schemas="schemas"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicForm, FormSchema } from '@/components/Form/index';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
label: '姓名',
|
||||
labelMessage: '这是一个提示',
|
||||
componentProps: {
|
||||
placeholder: '请输入姓名',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ required: true, message: '请输入姓名', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
component: 'NInputNumber',
|
||||
label: '手机',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机号码',
|
||||
showButton: false,
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
component: 'NSelect',
|
||||
label: '类型',
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: [
|
||||
{
|
||||
label: '舒适性',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '经济性',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeDate',
|
||||
component: 'NDatePicker',
|
||||
label: '预约时间',
|
||||
defaultValue: 1183135260000,
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
clearable: true,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeTime',
|
||||
component: 'NTimePicker',
|
||||
label: '停留时间',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeProject',
|
||||
component: 'NCheckbox',
|
||||
label: '预约项目',
|
||||
componentProps: {
|
||||
placeholder: '请选择预约项目',
|
||||
options: [
|
||||
{
|
||||
label: '种牙',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '补牙',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '根管',
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeSource',
|
||||
component: 'NRadioGroup',
|
||||
label: '来源',
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '网上',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '门店',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '状态',
|
||||
//插槽
|
||||
slot: 'statusSlot',
|
||||
},
|
||||
];
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
if (!values) {
|
||||
return message.error('请填写完整信息');
|
||||
}
|
||||
console.log(values);
|
||||
message.success(JSON.stringify(values));
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.BasicForm {
|
||||
width: 550px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
padding-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="基础表单"> useForm 表单,用于向用户收集表单信息 </n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="mt-4 proCard">
|
||||
<div class="BasicForm">
|
||||
<BasicForm @register="register" @submit="handleSubmit" @reset="handleReset">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
label: '姓名',
|
||||
labelMessage: '这是一个提示',
|
||||
giProps: {
|
||||
span: 1,
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请输入姓名',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ required: true, message: '请输入姓名', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
component: 'NInputNumber',
|
||||
label: '手机',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机号码',
|
||||
showButton: false,
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
component: 'NSelect',
|
||||
label: '类型',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: [
|
||||
{
|
||||
label: '舒适性',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '经济性',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeDate',
|
||||
component: 'NDatePicker',
|
||||
label: '预约时间',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
defaultValue: 1183135260000,
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
clearable: true,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeTime',
|
||||
component: 'NTimePicker',
|
||||
label: '停留时间',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeProject',
|
||||
component: 'NCheckbox',
|
||||
label: '预约项目',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请选择预约项目',
|
||||
options: [
|
||||
{
|
||||
label: '种牙',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '补牙',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '根管',
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeSource',
|
||||
component: 'NRadioGroup',
|
||||
label: '来源',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '网上',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '门店',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '状态',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
//插槽
|
||||
slot: 'statusSlot',
|
||||
},
|
||||
];
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: 1 },
|
||||
collapsedRows: 3,
|
||||
labelWidth: 120,
|
||||
layout: 'horizontal',
|
||||
submitButtonText: '提交预约',
|
||||
schemas,
|
||||
});
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
if (!values) {
|
||||
return message.error('请填写完整信息');
|
||||
}
|
||||
console.log(values);
|
||||
message.success(JSON.stringify(values));
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.BasicForm {
|
||||
width: 550px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
padding-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="loading" description="请稍候..">
|
||||
<Icons ref="iconsRef" />
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Icons from './icons.vue';
|
||||
|
||||
const iconsRef = ref();
|
||||
const loading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
iconsRef.value?.open('antd');
|
||||
loading.value = false;
|
||||
}, 10);
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
content-style=""
|
||||
>
|
||||
<template #header>
|
||||
<n-grid y-gap="20" x-gap="10" cols="24" item-responsive responsive="screen">
|
||||
<n-grid-item span="24 m:12 l:6">
|
||||
<n-input-group>
|
||||
<n-button>图标大小</n-button>
|
||||
<n-input-number style="width: 100%" v-model:value="compData.size" />
|
||||
</n-input-group>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="24 m:12 l:6">
|
||||
<n-color-picker
|
||||
:modes="['hex']"
|
||||
style="width: 100%"
|
||||
v-model:value="compData.color"
|
||||
:swatches="['#FFFFFF', '#18A058', '#2080F0', '#F0A020', '#D03050','#000000']"
|
||||
>
|
||||
<template #label>
|
||||
<div style="color: white">图标颜色 {{ compData.color }}</div>
|
||||
</template>
|
||||
</n-color-picker>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</template>
|
||||
<div class="icons">
|
||||
<n-grid y-gap="20" x-gap="0" cols="24" item-responsive responsive="screen">
|
||||
<n-grid-item v-for="(item, idx) in icons" :key="idx" span="12 m:4 l:3 xl:2">
|
||||
<div class="icons-item">
|
||||
<div class="icons-item_content">
|
||||
<n-icon class="icon" :color="compData.color" :size="compData.size">
|
||||
<component :is="item" />
|
||||
</n-icon>
|
||||
<span class="copy" v-copy="item.name" @click="handleCopy(item)">复制</span>
|
||||
</div>
|
||||
</div>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import * as AntdIcons from '@vicons/antd';
|
||||
import * as Ionicons5 from '@vicons/ionicons5';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||
|
||||
const designStore = useDesignSettingStore();
|
||||
const message = useMessage();
|
||||
|
||||
const icons = ref<any>([]);
|
||||
const compData = ref({
|
||||
color: designStore.appTheme,
|
||||
size: 32,
|
||||
});
|
||||
|
||||
const color = computed(() => {
|
||||
return compData.value.color;
|
||||
});
|
||||
|
||||
function handleCopy(icon) {
|
||||
message.success(`已复制,${icon.name}`);
|
||||
}
|
||||
|
||||
function open(type: string) {
|
||||
if (type === 'antd') {
|
||||
icons.value = AntdIcons;
|
||||
}
|
||||
if (type === 'ionicons5') {
|
||||
icons.value = Ionicons5;
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
:deep(.n-color-picker-trigger .n-color-picker-trigger__fill) {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.icons {
|
||||
&-item {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
&_content {
|
||||
padding: 20px 10px;
|
||||
border: 1px solid rgb(240 240 245);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.icon {
|
||||
transition: top 0.3s;
|
||||
position: relative;
|
||||
top: 0;
|
||||
}
|
||||
&:hover {
|
||||
.icon {
|
||||
top: -10px;
|
||||
}
|
||||
.copy {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
.copy {
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
height: 30px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
line-height: 30px;
|
||||
background-color: v-bind(color);
|
||||
transition: bottom 0.3s;
|
||||
color: rgb(240 240 245);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="loading" description="请稍候..">
|
||||
<Icons ref="iconsRef" />
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Icons from './icons.vue';
|
||||
|
||||
const iconsRef = ref();
|
||||
const loading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
iconsRef.value?.open('ionicons5');
|
||||
loading.value = false;
|
||||
}, 10);
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="图标选择器">
|
||||
图标选择器,用于表单选择图标,支持直接选定图标和输入图标名称来选择
|
||||
</n-card>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-card :bordered="false" class="mt-4 proCard" title="Antd">
|
||||
<IconSelector style="width: 100%" v-model:value="antdValue" option="antd" />
|
||||
</n-card>
|
||||
|
||||
<n-card :bordered="false" class="mt-4 proCard" title="Ionicons5">
|
||||
<IconSelector style="width: 100%" v-model:value="ionicons5Value" option="ionicons5" />
|
||||
</n-card>
|
||||
</n-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import IconSelector from '@/components/IconSelector/index.vue';
|
||||
|
||||
const antdValue = ref('');
|
||||
const ionicons5Value = ref('');
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="导入excel">
|
||||
将excel表格数据导入,可解析出完整的表格内容,包括所有的sheet和行列数据
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="mt-4 proCard">
|
||||
<n-upload
|
||||
directory-dnd
|
||||
:custom-request="handleUpload"
|
||||
name="file"
|
||||
type="file"
|
||||
accept=".xlsx, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3">
|
||||
<DownloadOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
<n-text style="font-size: 16px"> 点击或者拖动.xlsx文件到该区域来上传</n-text>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0"> 单次上传数据最大不建议超过5000条</n-p>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="false"
|
||||
class="proCard mt-4"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
title="表格数据"
|
||||
>
|
||||
<n-scrollbar style="max-height: 520px">
|
||||
<JsonViewer :value="response" :expand-depth="5" copyable boxed sort class="json-width" />
|
||||
</n-scrollbar>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { UploadCustomRequestOptions, useMessage } from 'naive-ui';
|
||||
import { DownloadOutlined } from '@vicons/antd';
|
||||
import { ImportExcel } from '@/api/addons/hgexample/comp';
|
||||
import type { UploadFileParams } from '@/utils/http/axios/types';
|
||||
import { JsonViewer } from 'vue3-json-viewer';
|
||||
import 'vue3-json-viewer/dist/vue3-json-viewer.css';
|
||||
|
||||
const message = useMessage();
|
||||
const response = ref<any>({});
|
||||
|
||||
function handleUpload(options: UploadCustomRequestOptions) {
|
||||
message.loading('正在导入,请稍候...', { duration: 1200 });
|
||||
const params: UploadFileParams = {
|
||||
file: options.file.file as File,
|
||||
};
|
||||
|
||||
ImportExcel(params).then((res) => {
|
||||
response.value = res;
|
||||
message.destroyAll();
|
||||
message.success('解析成功');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,369 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-p class="title">组件示例</n-p>
|
||||
<n-space vertical>
|
||||
<n-layout has-sider>
|
||||
<n-scrollbar :style="scrollbarStyle">
|
||||
<n-layout-sider
|
||||
bordered
|
||||
collapse-mode="width"
|
||||
:collapsed-width="64"
|
||||
:width="180"
|
||||
:collapsed="collapsed"
|
||||
show-trigger
|
||||
@collapse="collapsed = true"
|
||||
@expand="collapsed = false"
|
||||
>
|
||||
<n-menu
|
||||
v-model:value="activeKey"
|
||||
:collapsed="collapsed"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="22"
|
||||
:options="menuOptions"
|
||||
/>
|
||||
</n-layout-sider>
|
||||
</n-scrollbar>
|
||||
|
||||
<n-layout>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<component :is="currentComponent" />
|
||||
</n-card>
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import type { MenuOption } from 'naive-ui';
|
||||
import {
|
||||
ProfileOutlined,
|
||||
BorderOutlined,
|
||||
NumberOutlined,
|
||||
BorderOuterOutlined,
|
||||
CheckCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
FundProjectionScreenOutlined,
|
||||
ReadOutlined,
|
||||
StarOutlined,
|
||||
PushpinOutlined,
|
||||
LayoutOutlined,
|
||||
PrinterOutlined,
|
||||
DownloadOutlined,
|
||||
} from '@vicons/antd';
|
||||
import {
|
||||
HourglassOutline,
|
||||
NotificationsOutline,
|
||||
LocationOutline,
|
||||
SwapVertical,
|
||||
EllipsisHorizontalCircleOutline,
|
||||
AlbumsOutline,
|
||||
LogoChrome,
|
||||
} from '@vicons/ionicons5';
|
||||
import { renderIcon } from '@/utils';
|
||||
import FormBasic from './form/basic.vue';
|
||||
import FormUseForm from './form/useForm.vue';
|
||||
import Modal from './modal/index.vue';
|
||||
import Drag from './drag/index.vue';
|
||||
import Directive from './directive/index.vue';
|
||||
import ResultSuccess from './result/success.vue';
|
||||
import ResultFail from './result/fail.vue';
|
||||
import ResultInfo from './result/info.vue';
|
||||
import Exception403 from '../../../exception/403.vue';
|
||||
import Exception404 from '../../../exception/404.vue';
|
||||
import Exception500 from '../../../exception/500.vue';
|
||||
import VisPPchart from './vis/ppchart.vue';
|
||||
import VisEcharts from './vis/echarts.vue';
|
||||
import VisMadeAPie from './vis/madeapie.vue';
|
||||
import TextPinyin from './text/pinyin/index.vue';
|
||||
import TextMint from './text/mint/index.vue';
|
||||
import TextGradient from './text/gradient/index.vue';
|
||||
import TextHigh from './text/high/index.vue';
|
||||
import IconsAntd from './icons/antd.vue';
|
||||
import IconsIonicons5 from './icons/ionicons5.vue';
|
||||
import IconsSelector from './icons/selector.vue';
|
||||
import Watermark from './watermark/index.vue';
|
||||
import Des from './des/index.vue';
|
||||
import Calendar from './calendar/index.vue';
|
||||
import Timeline from './timeline/index.vue';
|
||||
import Notice from './notice/index.vue';
|
||||
import MapGaode from './map/gaode.vue';
|
||||
import MapBaidu from './map/baidu.vue';
|
||||
import Print from './print/index.vue';
|
||||
import TagsView from './tagsView/index.vue';
|
||||
import MoreComponents from './moreComponents/index.vue';
|
||||
import Waterfall from './waterfall/index.vue';
|
||||
import ImportExcel from './import/excel.vue';
|
||||
import FingerPrintJs from './fingerprintjs/index.vue';
|
||||
|
||||
const components = {
|
||||
formBasic: FormBasic,
|
||||
formUseForm: FormUseForm,
|
||||
modal: Modal,
|
||||
drag: Drag,
|
||||
directive: Directive,
|
||||
resultSuccess: ResultSuccess,
|
||||
resultFail: ResultFail,
|
||||
resultInfo: ResultInfo,
|
||||
exception403: Exception403,
|
||||
exception404: Exception404,
|
||||
exception500: Exception500,
|
||||
visPPchart: VisPPchart,
|
||||
visEcharts: VisEcharts,
|
||||
visMadeAPie: VisMadeAPie,
|
||||
textPinyin: TextPinyin,
|
||||
textMint: TextMint,
|
||||
textGradient: TextGradient,
|
||||
textHigh: TextHigh,
|
||||
iconsAntd: IconsAntd,
|
||||
iconsIonicons5: IconsIonicons5,
|
||||
iconsSelector: IconsSelector,
|
||||
watermark: Watermark,
|
||||
des: Des,
|
||||
calendar: Calendar,
|
||||
timeline: Timeline,
|
||||
notice: Notice,
|
||||
mapGaode: MapGaode,
|
||||
mapBaidu: MapBaidu,
|
||||
print: Print,
|
||||
tagsView: TagsView,
|
||||
moreComponents: MoreComponents,
|
||||
waterfall: Waterfall,
|
||||
importExcel: ImportExcel,
|
||||
fingerPrintJs: FingerPrintJs,
|
||||
};
|
||||
|
||||
const activeKey = ref<string>('formBasic');
|
||||
const collapsed = ref(false);
|
||||
|
||||
const scrollbarStyle = computed(() => {
|
||||
const height = '82vh';
|
||||
const width = collapsed.value ? '74px' : '188px';
|
||||
return {
|
||||
height: height,
|
||||
'max-height': height,
|
||||
'min-width': width,
|
||||
width: width,
|
||||
};
|
||||
});
|
||||
|
||||
const currentComponent = computed(() => {
|
||||
return components[activeKey.value] || null;
|
||||
});
|
||||
|
||||
const menuOptions: MenuOption[] = [
|
||||
{
|
||||
label: '表单',
|
||||
key: 'form',
|
||||
icon: renderIcon(ProfileOutlined),
|
||||
children: [
|
||||
{
|
||||
label: '基础使用',
|
||||
key: 'formBasic',
|
||||
},
|
||||
{
|
||||
label: 'useForm',
|
||||
key: 'formUseForm',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '图标',
|
||||
key: 'icons',
|
||||
icon: renderIcon(StarOutlined),
|
||||
children: [
|
||||
{
|
||||
label: 'Antd',
|
||||
key: 'iconsAntd',
|
||||
},
|
||||
{
|
||||
label: 'IonIcons5',
|
||||
key: 'iconsIonicons5',
|
||||
},
|
||||
{
|
||||
label: '选择器',
|
||||
key: 'iconsSelector',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '文字处理',
|
||||
key: 'text',
|
||||
icon: renderIcon(ReadOutlined),
|
||||
children: [
|
||||
{
|
||||
label: '汉字拼音',
|
||||
key: 'textPinyin',
|
||||
},
|
||||
{
|
||||
label: '敏感词汇',
|
||||
key: 'textMint',
|
||||
},
|
||||
{
|
||||
label: '渐变文字',
|
||||
key: 'textGradient',
|
||||
},
|
||||
{
|
||||
label: '文字高亮',
|
||||
key: 'textHigh',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '可视化',
|
||||
key: 'vis',
|
||||
icon: renderIcon(FundProjectionScreenOutlined),
|
||||
children: [
|
||||
{
|
||||
label: 'PPChart',
|
||||
key: 'visPPchart',
|
||||
},
|
||||
{
|
||||
label: 'Echarts',
|
||||
key: 'visEcharts',
|
||||
},
|
||||
{
|
||||
label: 'MadeAPie',
|
||||
key: 'visMadeAPie',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '异常页面',
|
||||
key: 'exception',
|
||||
icon: renderIcon(ExclamationCircleOutlined),
|
||||
children: [
|
||||
{
|
||||
label: '403',
|
||||
key: 'exception403',
|
||||
},
|
||||
{
|
||||
label: '404',
|
||||
key: 'exception404',
|
||||
},
|
||||
{
|
||||
label: '500',
|
||||
key: 'exception500',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '结果页面',
|
||||
key: 'result',
|
||||
icon: renderIcon(CheckCircleOutlined),
|
||||
children: [
|
||||
{
|
||||
label: '成功页',
|
||||
key: 'resultSuccess',
|
||||
},
|
||||
{
|
||||
label: '失败页',
|
||||
key: 'resultFail',
|
||||
},
|
||||
{
|
||||
label: '信息页',
|
||||
key: 'resultInfo',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '地图',
|
||||
key: 'map',
|
||||
icon: renderIcon(LocationOutline),
|
||||
children: [
|
||||
{
|
||||
label: '高德地图',
|
||||
key: 'mapGaode',
|
||||
},
|
||||
{
|
||||
label: '百度地图',
|
||||
key: 'mapBaidu',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '弹窗扩展',
|
||||
key: 'modal',
|
||||
icon: renderIcon(BorderOutlined),
|
||||
},
|
||||
{
|
||||
label: '消息提示',
|
||||
key: 'notice',
|
||||
icon: renderIcon(NotificationsOutline),
|
||||
},
|
||||
{
|
||||
label: '瀑布流',
|
||||
key: 'waterfall',
|
||||
icon: renderIcon(AlbumsOutline),
|
||||
},
|
||||
{
|
||||
label: '拖拽',
|
||||
key: 'drag',
|
||||
icon: renderIcon(NumberOutlined),
|
||||
},
|
||||
{
|
||||
label: '水印',
|
||||
key: 'watermark',
|
||||
icon: renderIcon(PushpinOutlined),
|
||||
},
|
||||
{
|
||||
label: '日历',
|
||||
key: 'calendar',
|
||||
icon: renderIcon(ReadOutlined),
|
||||
},
|
||||
{
|
||||
label: '打印',
|
||||
key: 'print',
|
||||
icon: renderIcon(PrinterOutlined),
|
||||
},
|
||||
|
||||
{
|
||||
label: '时间线',
|
||||
key: 'timeline',
|
||||
icon: renderIcon(HourglassOutline),
|
||||
},
|
||||
{
|
||||
label: 'tagsView',
|
||||
key: 'tagsView',
|
||||
icon: renderIcon(SwapVertical),
|
||||
},
|
||||
{
|
||||
label: '导入Excel',
|
||||
key: 'importExcel',
|
||||
icon: renderIcon(DownloadOutlined),
|
||||
},
|
||||
{
|
||||
label: '卡片描述',
|
||||
key: 'des',
|
||||
icon: renderIcon(LayoutOutlined),
|
||||
},
|
||||
{
|
||||
label: '指令示例',
|
||||
key: 'directive',
|
||||
icon: renderIcon(BorderOuterOutlined),
|
||||
},
|
||||
{
|
||||
label: '浏览器指纹',
|
||||
key: 'fingerPrintJs',
|
||||
icon: renderIcon(LogoChrome),
|
||||
},
|
||||
{
|
||||
label: '更多组件',
|
||||
key: 'moreComponents',
|
||||
icon: renderIcon(EllipsisHorizontalCircleOutline),
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.title {
|
||||
font-size: 18px;
|
||||
transition: color 0.3s var(--n-bezier);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: var(--n-title-text-color);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="container" ref="container"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import useCreateScript from '@/hooks/useCreateScript';
|
||||
|
||||
const SCRIPT_URL =
|
||||
'http://api.map.baidu.com/getscript?v=3.0&ak=WxbQmaOc3bvSGSaKWcyeFSf8fnYCWpKd&services=&t=' +
|
||||
new Date().getTime();
|
||||
const container = ref<HTMLDivElement | null>(null);
|
||||
const { createScriptPromise } = useCreateScript(SCRIPT_URL);
|
||||
const initMap = () => {
|
||||
createScriptPromise.then(() => {
|
||||
const bMap = (window as any).BMap;
|
||||
const map: any = new bMap.Map(container.value);
|
||||
const point = new bMap.Point(116.404, 39.915);
|
||||
map.centerAndZoom(point, 7);
|
||||
map.enableScrollWheelZoom();
|
||||
map.setMapStyleV2({ styleId: 'ea4652613f3629247d47666706ce7e89' });
|
||||
});
|
||||
};
|
||||
onMounted(initMap);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 720px;
|
||||
margin-top: -32px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="container" ref="container"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import useCreateScript from '@/hooks/useCreateScript';
|
||||
|
||||
const SCRIPT_URL = 'https://webapi.amap.com/maps?v=1.4.15&key=9f2d3fcc4b12a7c915fded4b55902e21';
|
||||
const container = ref<HTMLDivElement | null>(null);
|
||||
const height = ref(0);
|
||||
const { createScriptPromise } = useCreateScript(SCRIPT_URL);
|
||||
const initMap = () => {
|
||||
height.value = container.value?.parentElement?.getBoundingClientRect().height || 0;
|
||||
createScriptPromise.then(() => {
|
||||
const aMap: any = (window as any).AMap;
|
||||
new aMap.Map(container.value, {
|
||||
zoom: 22,
|
||||
center: [116.397428, 39.90923],
|
||||
viewMode: '3D',
|
||||
pitch: 75,
|
||||
});
|
||||
});
|
||||
};
|
||||
onMounted(initMap);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 720px;
|
||||
margin-top: -32px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="模态框">
|
||||
模态框,用于向用户收集或展示信息,Modal 采用 Dialog 预设,扩展拖拽效果
|
||||
<br />
|
||||
以下是 useModal
|
||||
方式,ref方式,也支持,使用方式和其他组件一致,如:modalRef.value.closeModal()
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="mt-4 proCard">
|
||||
<n-alert title="Modal嵌套Form" type="info">
|
||||
使用 useModal 进行弹窗展示和操作,并演示了在Modal内和Form组件,组合使用方法
|
||||
</n-alert>
|
||||
<n-divider />
|
||||
<n-space>
|
||||
<n-button type="primary" @click="showModal">打开Modal嵌套Form例子</n-button>
|
||||
</n-space>
|
||||
<n-divider />
|
||||
<n-alert title="个性化轻量级" type="info">
|
||||
使用 useModal 进行弹窗展示和操作,自定义配置,实现轻量级效果,更多配置,请参考文档
|
||||
</n-alert>
|
||||
<n-divider />
|
||||
<n-space>
|
||||
<n-button type="primary" @click="showLightModal">轻量级确认</n-button>
|
||||
</n-space>
|
||||
<n-divider />
|
||||
<n-alert title="提示" type="info">
|
||||
组件暴露了,setProps 方法,用于修改组件内部
|
||||
Props,比如标题,等,具体参考UI框架文档,DialogReactive Properties
|
||||
</n-alert>
|
||||
</n-card>
|
||||
|
||||
<basicModal @register="modalRegister" ref="modalRef" class="basicModal" @on-ok="okModal">
|
||||
<template #default>
|
||||
<BasicForm @register="register" @reset="handleReset" class="basicForm">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
</template>
|
||||
</basicModal>
|
||||
|
||||
<basicModal
|
||||
@register="lightModalRegister"
|
||||
class="basicModalLight"
|
||||
ref="modalRef"
|
||||
@on-ok="lightOkModal"
|
||||
>
|
||||
<template #default>
|
||||
<p class="text-gray-500" style="padding-left: 35px">一些对话框内容</p>
|
||||
</template>
|
||||
</basicModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, toRefs } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { basicModal, useModal } from '@/components/Modal';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
label: '姓名',
|
||||
labelMessage: '这是一个提示',
|
||||
giProps: {
|
||||
span: 1,
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请输入姓名',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ required: true, message: '请输入姓名', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
component: 'NInputNumber',
|
||||
label: '手机',
|
||||
componentProps: {
|
||||
placeholder: '请输入手机号码',
|
||||
showButton: false,
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
component: 'NSelect',
|
||||
label: '类型',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: [
|
||||
{
|
||||
label: '舒适性',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '经济性',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeDate',
|
||||
component: 'NDatePicker',
|
||||
label: '预约时间',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
defaultValue: 1183135260000,
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
clearable: true,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeTime',
|
||||
component: 'NTimePicker',
|
||||
label: '停留时间',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeProject',
|
||||
component: 'NCheckbox',
|
||||
label: '预约项目',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请选择预约项目',
|
||||
options: [
|
||||
{
|
||||
label: '种牙',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '补牙',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '根管',
|
||||
value: 3,
|
||||
},
|
||||
],
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'makeSource',
|
||||
component: 'NRadioGroup',
|
||||
label: '来源',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '网上',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '门店',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
label: '状态',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
//插槽
|
||||
slot: 'statusSlot',
|
||||
},
|
||||
];
|
||||
|
||||
export default defineComponent({
|
||||
components: { basicModal, BasicForm },
|
||||
setup() {
|
||||
const modalRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
|
||||
const [modalRegister, { openModal, closeModal, setSubLoading }] = useModal({
|
||||
title: '新增预约',
|
||||
});
|
||||
|
||||
const [
|
||||
lightModalRegister,
|
||||
{
|
||||
openModal: lightOpenModal,
|
||||
closeModal: lightCloseModal,
|
||||
setSubLoading: lightSetSubLoading,
|
||||
},
|
||||
] = useModal({
|
||||
title: '确认对话框',
|
||||
showIcon: true,
|
||||
type: 'warning',
|
||||
closable: false,
|
||||
maskClosable: true,
|
||||
});
|
||||
|
||||
const [register, { submit }] = useForm({
|
||||
gridProps: { cols: 1 },
|
||||
collapsedRows: 3,
|
||||
labelWidth: 120,
|
||||
layout: 'horizontal',
|
||||
submitButtonText: '提交预约',
|
||||
showActionButtonGroup: false,
|
||||
schemas,
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
formValue: {
|
||||
name: '小马哥',
|
||||
},
|
||||
});
|
||||
|
||||
async function okModal() {
|
||||
const formRes = await submit();
|
||||
if (formRes) {
|
||||
closeModal();
|
||||
console.log('formRes', formRes);
|
||||
message.success('提交成功');
|
||||
} else {
|
||||
message.error('验证失败,请填写完整信息');
|
||||
setSubLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
function lightOkModal() {
|
||||
lightCloseModal();
|
||||
lightSetSubLoading(false);
|
||||
}
|
||||
|
||||
function showLightModal() {
|
||||
lightOpenModal();
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
openModal();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
console.log(values);
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
modalRef,
|
||||
register,
|
||||
modalRegister,
|
||||
lightModalRegister,
|
||||
handleReset,
|
||||
showModal,
|
||||
okModal,
|
||||
lightOkModal,
|
||||
showLightModal,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.basicForm {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.n-dialog.basicModal {
|
||||
width: 640px;
|
||||
}
|
||||
|
||||
.n-dialog.basicModalLight {
|
||||
width: 416px;
|
||||
padding-top: 26px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<Iframe ref="iframeRef" style="zoom: 0.8" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Iframe from '../../../../iframe/index.vue';
|
||||
|
||||
const iframeRef = ref();
|
||||
|
||||
onMounted(() => {
|
||||
iframeRef.value?.open('https://www.naiveui.com/zh-CN/os-theme/components/button');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-grid :cols="2" :x-gap="10" :y-gap="10">
|
||||
<n-gi>
|
||||
<n-card
|
||||
title="警告信息alert"
|
||||
:content-style="{ padding: '10px' }"
|
||||
:header-style="{ padding: '10px' }"
|
||||
>
|
||||
<n-space vertical :size="12">
|
||||
<n-alert title="Info 类型" type="info"> 万事开头难,只能一直努力!!! </n-alert>
|
||||
<n-alert title="Success 类型" type="success">
|
||||
成功就是眼前,过了黑夜就是白天,加油~~
|
||||
</n-alert>
|
||||
<n-alert title="Warning 类型" type="warning">
|
||||
想想昨天,看看今天,望望明天;一切都会好起来
|
||||
</n-alert>
|
||||
<n-alert title="Error 类型" type="error"> 有人欺负人,请不犹豫,请还回去 </n-alert>
|
||||
<n-alert title="可以关掉" type="info" closable> Gee it's good to be back home </n-alert>
|
||||
<n-alert title="没有边框" type="info" :bordered="false">
|
||||
Gee it's good to be back home
|
||||
</n-alert>
|
||||
<n-alert title="自定义图标">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<BalloonOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
Well the Ukraine girls really knock me out<br />
|
||||
They leave the West behind<br />
|
||||
And Moscow girls make me sing and shout<br />
|
||||
That Georgia's always on my mind<br />
|
||||
Aw come on!
|
||||
</n-alert>
|
||||
|
||||
<n-alert :show-icon="false" title="没有图标">
|
||||
Yeah I'm back in the U.S.S.R.<br />
|
||||
You don't know how lucky you are boys
|
||||
</n-alert>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-card
|
||||
title="对话框Dialog"
|
||||
:content-style="{ padding: '10px' }"
|
||||
:header-style="{ padding: '10px' }"
|
||||
>
|
||||
<div class="padding">
|
||||
<n-space>
|
||||
<n-button size="small" type="primary" @click="openConfirm('warning')">警告</n-button>
|
||||
<n-button size="small" type="warning" @click="openConfirm('success')">成功</n-button>
|
||||
<n-button size="small" type="info" @click="openConfirm('error')">错误</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card
|
||||
class="mt-2"
|
||||
title="信息Message"
|
||||
:content-style="{ padding: '10px' }"
|
||||
:header-style="{ padding: '10px' }"
|
||||
>
|
||||
<n-space>
|
||||
<n-button type="info" @click="openMessage('info')"> 信息 </n-button>
|
||||
<n-button type="error" @click="openMessage('error')"> 错误 </n-button>
|
||||
<n-button type="warning" @click="openMessage('warning')"> 警告 </n-button>
|
||||
<n-button type="success" @click="openMessage('success')"> 成功 </n-button>
|
||||
<n-button type="primary" @click="openMessage('loading')"> 加载中 </n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
<n-card
|
||||
class="mt-2"
|
||||
title="通知Notification"
|
||||
:content-style="{ padding: '10px' }"
|
||||
:header-style="{ padding: '10px' }"
|
||||
>
|
||||
<n-space>
|
||||
<n-button type="info" @click="openNotification('info')"> 信息 </n-button>
|
||||
<n-button type="error" @click="openNotification('error')"> 错误 </n-button>
|
||||
<n-button type="warning" @click="openNotification('warning')"> 警告 </n-button>
|
||||
<n-button type="success" @click="openNotification('success')"> 成功 </n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
class="mt-2"
|
||||
title="弹出确认Popconfirm"
|
||||
:content-style="{ padding: '10px' }"
|
||||
:header-style="{ padding: '10px' }"
|
||||
>
|
||||
<n-space>
|
||||
<n-popconfirm
|
||||
@positive-click="handlePositiveClick"
|
||||
@negative-click="handleNegativeClick"
|
||||
>
|
||||
<template #trigger>
|
||||
<n-button>引用</n-button>
|
||||
</template>
|
||||
一切都将一去杳然,任何人都无法将其捕获。
|
||||
</n-popconfirm>
|
||||
<n-popconfirm :negative-text="null" @positive-click="handlePositiveClick">
|
||||
<template #trigger>
|
||||
<n-button>只有确定</n-button>
|
||||
</template>
|
||||
一切都将一去杳然,任何人都无法将其捕获。
|
||||
</n-popconfirm>
|
||||
<n-popconfirm :positive-text="null" @negative-click="handleNegativeClick">
|
||||
<template #trigger>
|
||||
<n-button>只有取消</n-button>
|
||||
</template>
|
||||
一切都将一去杳然,任何人都无法将其捕获。
|
||||
</n-popconfirm>
|
||||
<n-popconfirm :positive-text="null" :negative-text="null">
|
||||
<template #trigger>
|
||||
<n-button>什么也没有</n-button>
|
||||
</template>
|
||||
一切都将一去杳然,任何人都无法将其捕获。
|
||||
</n-popconfirm>
|
||||
<n-popconfirm>
|
||||
<template #trigger>
|
||||
<n-button>自定义 action</n-button>
|
||||
</template>
|
||||
<template #action> 自定义 action </template>
|
||||
一切都将一去杳然,任何人都无法将其捕获。
|
||||
</n-popconfirm>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
class="mt-2"
|
||||
title="弹出信息Popover"
|
||||
:content-style="{ padding: '10px' }"
|
||||
:header-style="{ padding: '10px' }"
|
||||
>
|
||||
<n-space>
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button>悬浮</n-button>
|
||||
</template>
|
||||
<span>I wish they all could be California girls</span>
|
||||
</n-popover>
|
||||
<n-popover trigger="hover" :keep-alive-on-hover="false">
|
||||
<template #trigger>
|
||||
<n-button>悬浮(忽略主体)</n-button>
|
||||
</template>
|
||||
<span>I wish they all could be California girls</span>
|
||||
</n-popover>
|
||||
<n-popover trigger="click">
|
||||
<template #trigger>
|
||||
<n-button>点击</n-button>
|
||||
</template>
|
||||
<span>I wish they all could be California girls</span>
|
||||
</n-popover>
|
||||
<n-popover trigger="focus">
|
||||
<template #trigger>
|
||||
<n-button>聚焦</n-button>
|
||||
</template>
|
||||
<span>I wish they all could be California girls</span>
|
||||
</n-popover>
|
||||
<n-popover trigger="manual" :show="showPopover">
|
||||
<template #trigger>
|
||||
<n-button @click="showPopover = !showPopover"> 手动 </n-button>
|
||||
</template>
|
||||
<span>I wish they all could be California girls</span>
|
||||
</n-popover>
|
||||
<n-popover trigger="hover" :delay="500" :duration="500">
|
||||
<template #trigger>
|
||||
<n-button>延迟 500ms, 持续 500ms</n-button>
|
||||
</template>
|
||||
<span>
|
||||
Lately did you ever feel the pain In the morning rain as it soaks it to the bone
|
||||
</span>
|
||||
</n-popover>
|
||||
</n-space>
|
||||
</n-card>
|
||||
<n-card
|
||||
class="mt-2"
|
||||
title="弹出提示Tooltip"
|
||||
:content-style="{ padding: '10px' }"
|
||||
:header-style="{ padding: '10px' }"
|
||||
>
|
||||
<n-space>
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button> 鸭子 </n-button>
|
||||
</template>
|
||||
如果它长得像鸭子,走起来像鸭子,叫起来也像鸭子,那它一定是个鸭子。
|
||||
</n-tooltip>
|
||||
<n-tooltip placement="bottom" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button> 悬浮 </n-button>
|
||||
</template>
|
||||
<span> I wish they all could be California girls </span>
|
||||
</n-tooltip>
|
||||
<n-tooltip placement="bottom" trigger="click">
|
||||
<template #trigger>
|
||||
<n-button> 点击 </n-button>
|
||||
</template>
|
||||
<span> I wish they all could be California girls </span>
|
||||
</n-tooltip>
|
||||
<n-tooltip :show="showPopover" placement="bottom">
|
||||
<template #trigger>
|
||||
<n-button @click="showPopover = !showPopover"> 手动 </n-button>
|
||||
</template>
|
||||
<span> I wish they all could be California girls </span>
|
||||
</n-tooltip>
|
||||
<n-tooltip :show-arrow="false" trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button>默认有箭头</n-button>
|
||||
</template>
|
||||
和 Popover 一样
|
||||
</n-tooltip>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useDialog, useMessage, useNotification } from 'naive-ui';
|
||||
import { BalloonOutline } from '@vicons/ionicons5';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const notification = useNotification();
|
||||
const showPopover = ref(false);
|
||||
|
||||
function openConfirm(type: string) {
|
||||
switch (type) {
|
||||
case 'warning':
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要离开我?',
|
||||
positiveText: '确定',
|
||||
negativeText: '不确定',
|
||||
onPositiveClick: () => {
|
||||
message.success('确定');
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
message.error('不确定');
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'success':
|
||||
dialog.success({
|
||||
title: '成功',
|
||||
content: '小哥哥你太棒了~~',
|
||||
positiveText: '哇哦~',
|
||||
});
|
||||
break;
|
||||
case 'error':
|
||||
dialog.error({
|
||||
title: '错误',
|
||||
content: '知道错了吗,下次改了',
|
||||
positiveText: '快哭了',
|
||||
onPositiveClick: () => {
|
||||
message.success('我知道了');
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function openMessage(type: string) {
|
||||
switch (type) {
|
||||
case 'info':
|
||||
message.info('在心碎中认清遗憾 生命漫长也短暂跳动心脏长出藤蔓');
|
||||
break;
|
||||
case 'error':
|
||||
message.error('愿为险而战 跌入灰暗坠入深渊');
|
||||
break;
|
||||
case 'warning':
|
||||
message.warning('沾满泥土的脸 没有神的光环');
|
||||
break;
|
||||
case 'success':
|
||||
message.success('握紧手中的平凡 此心自称无憾生命的火已点燃');
|
||||
break;
|
||||
case 'loading':
|
||||
message.loading('有一天也许会走远 也许还能再相见');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function openNotification(type: string) {
|
||||
switch (type) {
|
||||
case 'info':
|
||||
notification.info({
|
||||
content: '在心碎中认清遗憾 生命漫长也短暂跳动心脏长出藤蔓',
|
||||
meta: '只要平凡',
|
||||
});
|
||||
break;
|
||||
case 'error':
|
||||
notification.error({
|
||||
content: '愿为险而战 跌入灰暗坠入深渊',
|
||||
meta: '只要平凡',
|
||||
});
|
||||
break;
|
||||
case 'warning':
|
||||
notification.warning({
|
||||
content: '沾满泥土的脸 没有神的光环',
|
||||
meta: '只要平凡',
|
||||
});
|
||||
break;
|
||||
case 'success':
|
||||
notification.success({
|
||||
content: '握紧手中的平凡 此心自称无憾生命的火已点燃',
|
||||
meta: '只要平凡',
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePositiveClick() {
|
||||
message.info('是的');
|
||||
}
|
||||
|
||||
function handleNegativeClick() {
|
||||
message.info('并不');
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="main-container">
|
||||
<n-card title="打印图片" :header-style="{ padding: '5px' }" :content-style="{ padding: '0px' }">
|
||||
<template #header-extra>
|
||||
<n-button type="primary" size="small" @click="printImage">打印</n-button>
|
||||
</template>
|
||||
<div class="image-wrapper">
|
||||
<img :src="imagePath" />
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card
|
||||
title="打印HTML"
|
||||
:header-style="{ padding: '5px' }"
|
||||
:content-style="{ padding: '0px' }"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-button type="primary" size="small" @click="printHtml">打印</n-button>
|
||||
</template>
|
||||
<div id="htmlWrapper" class="flex justify-center html-wrapper align-center flex-direction">
|
||||
<n-table :data="dataList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>姓名</th>
|
||||
<th>年龄</th>
|
||||
<th>性别</th>
|
||||
<th>职业</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) of dataList" :key="index">
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.age }}</td>
|
||||
<td>{{ item.gender }}</td>
|
||||
<td>{{ item.career }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import printJS from 'print-js';
|
||||
import imagePath from '@/assets/images/logo.png';
|
||||
import { defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'Print',
|
||||
setup() {
|
||||
function printImage() {
|
||||
printJS({
|
||||
printable: imagePath,
|
||||
type: 'image',
|
||||
showModal: false,
|
||||
});
|
||||
}
|
||||
function printHtml() {
|
||||
printJS({
|
||||
printable: 'htmlWrapper',
|
||||
type: 'html',
|
||||
targetStyles: ['*'],
|
||||
});
|
||||
}
|
||||
return {
|
||||
printImage,
|
||||
printHtml,
|
||||
imagePath,
|
||||
dataList: [
|
||||
{
|
||||
name: '张三',
|
||||
age: 30,
|
||||
gender: '男',
|
||||
career: '工程师',
|
||||
},
|
||||
{
|
||||
name: '李四',
|
||||
age: 20,
|
||||
gender: '男',
|
||||
career: '服务员',
|
||||
},
|
||||
{
|
||||
name: '王五',
|
||||
age: 40,
|
||||
gender: '女',
|
||||
career: '售货员',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.image-wrapper {
|
||||
width: 30%;
|
||||
margin: 0 auto;
|
||||
& > img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.html-wrapper {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
& > h1 {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<div class="result-box">
|
||||
<n-result status="error" title="操作失败" description="请核对并修改以下信息后,再重新提交。">
|
||||
<div class="result-box-extra">
|
||||
<p>您提交的内容有如下错误:</p>
|
||||
<p class="mt-3">
|
||||
<n-space align="center">
|
||||
<n-icon size="20" color="#f0a020">
|
||||
<InfoCircleOutlined />
|
||||
</n-icon>
|
||||
<span>认证照片不够清晰</span>
|
||||
<n-button type="info" text>立即修改</n-button>
|
||||
</n-space>
|
||||
</p>
|
||||
<p class="mt-3">
|
||||
<n-space>
|
||||
<n-icon size="20" color="#f0a020">
|
||||
<InfoCircleOutlined />
|
||||
</n-icon>
|
||||
<span>备注包含敏感字符,并且不能包含政治相关</span>
|
||||
<n-button type="info" text>立即修改</n-button>
|
||||
</n-space>
|
||||
</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-center mb-4">
|
||||
<n-space align="center">
|
||||
<n-button type="info" @click="goHome">回到首页</n-button>
|
||||
<n-button>查看详情</n-button>
|
||||
<n-button>打印</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
</n-result>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { InfoCircleOutlined } from '@vicons/antd';
|
||||
|
||||
const router = useRouter();
|
||||
const themeVars = useThemeVars();
|
||||
|
||||
const getTableHeaderColor = computed(() => {
|
||||
return themeVars.value.tableHeaderColor;
|
||||
});
|
||||
|
||||
function goHome() {
|
||||
router.push('/');
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.result-box {
|
||||
width: 72%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
padding-top: 5px;
|
||||
|
||||
&-extra {
|
||||
padding: 24px 40px;
|
||||
text-align: left;
|
||||
background: v-bind(getTableHeaderColor);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<div class="result-box">
|
||||
<n-result
|
||||
status="info"
|
||||
title="提示"
|
||||
description="本次提交,将在24小时候内自动转入对方账户,如操作失误,请及时撤回"
|
||||
>
|
||||
<div class="result-box-extra">
|
||||
<p>您提交的内容如下:</p>
|
||||
<p class="mt-3">
|
||||
<n-space align="center">
|
||||
<n-icon size="20" color="#18a058">
|
||||
<CheckCircleOutlined />
|
||||
</n-icon>
|
||||
<span>转入支付宝账户(189****5426):¥1980元</span>
|
||||
<n-button type="info" text>立即撤回</n-button>
|
||||
</n-space>
|
||||
</p>
|
||||
<p class="mt-3">
|
||||
<n-space>
|
||||
<n-icon size="20" color="#18a058">
|
||||
<CheckCircleOutlined />
|
||||
</n-icon>
|
||||
<span>转入支付宝账户(187****5426):¥2980元</span>
|
||||
<n-button type="info" text>立即撤回</n-button>
|
||||
</n-space>
|
||||
</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-center mb-4">
|
||||
<n-space align="center">
|
||||
<n-button type="info" @click="goHome">回到首页</n-button>
|
||||
<n-button>查看详情</n-button>
|
||||
<n-button>全部撤回</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
</n-result>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { CheckCircleOutlined } from '@vicons/antd';
|
||||
|
||||
const router = useRouter();
|
||||
const themeVars = useThemeVars();
|
||||
|
||||
const getTableHeaderColor = computed(() => {
|
||||
return themeVars.value.tableHeaderColor;
|
||||
});
|
||||
|
||||
function goHome() {
|
||||
router.push('/');
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.result-box {
|
||||
width: 72%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
padding-top: 5px;
|
||||
|
||||
&-extra {
|
||||
padding: 24px 40px;
|
||||
text-align: left;
|
||||
background: v-bind(getTableHeaderColor);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<div class="result-box">
|
||||
<n-result
|
||||
status="success"
|
||||
title="操作成功"
|
||||
description="提交结果页用于反馈一系列操作任务的处理结果,如果仅是简单操作,灰色区域可以显示一些补充的信息。"
|
||||
>
|
||||
<div class="result-box-extra">
|
||||
<p>已提交申请,等待财务部门审核。</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-center mb-4">
|
||||
<n-space align="center">
|
||||
<n-button type="info" @click="goHome">回到首页</n-button>
|
||||
<n-button>查看详情</n-button>
|
||||
<n-button>打印</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
</n-result>
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useThemeVars } from 'naive-ui';
|
||||
|
||||
const router = useRouter();
|
||||
const themeVars = useThemeVars();
|
||||
|
||||
const getTableHeaderColor = computed(() => {
|
||||
return themeVars.value.tableHeaderColor;
|
||||
});
|
||||
|
||||
function goHome() {
|
||||
router.push('/');
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.result-box {
|
||||
width: 72%;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
padding-top: 5px;
|
||||
|
||||
&-extra {
|
||||
padding: 24px 40px;
|
||||
text-align: left;
|
||||
background: v-bind(getTableHeaderColor);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="tagsView"> 标签页操作项 </n-card>
|
||||
</div>
|
||||
<n-card>
|
||||
<n-space>
|
||||
<n-button type="info" @click="handleSignal('1')"> 刷新页面路由 </n-button>
|
||||
<n-button type="error" @click="handleSignal('2')"> 关闭当前页面 </n-button>
|
||||
<n-button type="warning" @click="handleSignal('3')"> 关闭其他页面 </n-button>
|
||||
<n-button type="success" @click="handleSignal('4')"> 关闭全部页面 </n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTabsViewStore } from '@/store/modules/tabsView';
|
||||
|
||||
const tabsViewStore = useTabsViewStore();
|
||||
|
||||
function handleSignal(signal: string) {
|
||||
tabsViewStore.closeSignal(signal);
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-space vertical>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 渐变文字 </template>
|
||||
<template #header-extra>
|
||||
<n-gradient-text type="success"> 渐变文字成了 </n-gradient-text>
|
||||
</template>
|
||||
<n-space vertical>
|
||||
<n-gradient-text style="white-space: normal" type="success">
|
||||
<p
|
||||
>苏轼,(1037年1月8日-1101年8月24日)字子瞻、和仲,号铁冠道人、东坡居士,世称苏东坡、苏仙,汉族,眉州眉山(四川省眉山市)人,祖籍河北栾城,北宋著名文学家、书法家、画家,历史治水名人。苏轼是北宋中期文坛领袖,在诗、词、散文、书、画等方面取得很高成就。文纵横恣肆;诗题材广阔,清新豪健,善用夸张比喻,独具风格,与黄庭坚并称“苏黄”;词开豪放一派,与辛弃疾同是豪放派代表,并称“苏辛”;散文著述宏富,豪放自如,与欧阳修并称“欧苏”,为“唐宋八大家”之一。苏轼善书,“宋四家”之一;擅长文人画,尤擅墨竹、怪石、枯木等。与韩愈、柳宗元和欧阳修合称“千古文章四大家”。作品有《东坡七集》《东坡易传》《东坡乐府》《潇湘竹石图卷》《古木怪石图卷》等</p
|
||||
>
|
||||
</n-gradient-text>
|
||||
</n-space>
|
||||
</n-card>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 渐变文字 </template>
|
||||
<template #header-extra>
|
||||
<n-gradient-text type="warning"> 渐变文字成了 </n-gradient-text>
|
||||
</template>
|
||||
<n-space vertical>
|
||||
<n-gradient-text style="white-space: normal" type="warning">
|
||||
<p
|
||||
>白居易(772年-846年),字乐天,号香山居士,又号醉吟先生,祖籍太原,到其曾祖父时迁居下邽,生于河南新郑。是唐代伟大的现实主义诗人,唐代三大诗人之一。白居易与元稹共同倡导新乐府运动,世称“元白”,与刘禹锡并称“刘白”。白居易的诗歌题材广泛,形式多样,语言平易通俗,有“诗魔”和“诗王”之称。官至翰林学士、左赞善大夫。公元846年,白居易在洛阳逝世,葬于香山。有《白氏长庆集》传世,代表诗作有《长恨歌》《卖炭翁》《琵琶行》等。</p
|
||||
>
|
||||
</n-gradient-text>
|
||||
</n-space>
|
||||
</n-card>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 自定义颜色 </template>
|
||||
<template #header-extra>
|
||||
<n-gradient-text gradient="linear-gradient(90deg, red 0%, green 50%, blue 100%)">
|
||||
渐变文字成了
|
||||
</n-gradient-text>
|
||||
</template>
|
||||
<n-space vertical>
|
||||
<n-gradient-text
|
||||
gradient="linear-gradient(90deg, red 0%, green 50%, blue 100%)"
|
||||
style="white-space: normal"
|
||||
>
|
||||
<p
|
||||
>杜甫(712-770),字子美,自号少陵野老,世称“杜工部”、“杜少陵”等,汉族,河南府巩县(今河南省巩义市)人,唐代伟大的现实主义诗人,杜甫被世人尊为“诗圣”,其诗被称为“诗史”。杜甫与李白合称“李杜”,为了跟另外两位诗人李商隐与杜牧即“小李杜”区别开来,杜甫与李白又合称“大李杜”。他忧国忧民,人格高尚,他的约1400余首诗被保留了下来,诗艺精湛,在中国古典诗歌中备受推崇,影响深远。759-766年间曾居成都,后世有杜甫草堂纪念</p
|
||||
>
|
||||
</n-gradient-text>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup></script>
|
||||
<style lang="less" scoped>
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
text-indent: 28px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card title="高亮" style="margin-bottom: 10px">
|
||||
<n-text>
|
||||
<p style="line-height: 2" v-html="compData1.text"></p>
|
||||
</n-text>
|
||||
</n-card>
|
||||
<n-card title="自定义颜色" style="margin-bottom: 10px">
|
||||
<n-text>
|
||||
<p style="line-height: 2" v-html="compData2.text"></p>
|
||||
</n-text>
|
||||
</n-card>
|
||||
<n-card title="自定义样式">
|
||||
<n-text>
|
||||
<p style="line-height: 2" v-html="compData3.text"></p>
|
||||
</n-text>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import highHtml from '@/utils/highHtml';
|
||||
import { reactive, onMounted } from 'vue';
|
||||
|
||||
const text =
|
||||
'先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。\n' +
|
||||
'\n' +
|
||||
' 宫中府中,俱为一体;陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。\n' +
|
||||
'\n' +
|
||||
' 侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。\n' +
|
||||
'\n' +
|
||||
' 将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。\n' +
|
||||
'\n' +
|
||||
' 亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。\n' +
|
||||
'\n' +
|
||||
' 臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。\n' +
|
||||
'\n' +
|
||||
' 先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明;故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。\n' +
|
||||
'\n' +
|
||||
' 愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏。臣不胜受恩感激。今当远离,临表涕零,不知所言';
|
||||
|
||||
const compData1 = reactive({
|
||||
text: '',
|
||||
keys: ['先帝', '先汉', '苟'],
|
||||
});
|
||||
const compData2 = reactive({
|
||||
text: '',
|
||||
keys: [
|
||||
{
|
||||
keyword: '先帝',
|
||||
bgColor: 'red',
|
||||
color: '#fff',
|
||||
renderHighlightKeyword: (str) => {
|
||||
return `<span class="high" style="padding:2px 5px;margin:0 5px;border-radius:3px;background-color: #3a5ccc;color: #ffffff">${str}</span>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
keyword: '中',
|
||||
bgColor: '#b4d5ff',
|
||||
color: '#fff',
|
||||
renderHighlightKeyword: (str) => {
|
||||
return `<span class="high" style="padding:2px 5px;margin:0 5px;border-radius:3px;background-color: #e96656;color: #ffffff">${str}</span>`;
|
||||
},
|
||||
},
|
||||
{ keyword: '苟', bgColor: '#b4d5ff', color: '#fff' },
|
||||
],
|
||||
});
|
||||
const compData3 = reactive({
|
||||
text: '',
|
||||
keys: [
|
||||
{
|
||||
keyword: '之',
|
||||
bgColor: 'red',
|
||||
color: '#fff',
|
||||
style: { padding: '2px 5px', margin: '0 5px', 'border-radius': '3px' },
|
||||
},
|
||||
{ keyword: '先帝', bgColor: '#b4d5ff', color: '#fff' },
|
||||
{ keyword: '苟', bgColor: '#b4d5ff', color: '#fff' },
|
||||
],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
compData1.text = highHtml(text, compData1.keys).highText;
|
||||
compData2.text = highHtml(text, compData2.keys).highText;
|
||||
compData3.text = highHtml(text, compData3.keys).highText;
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
.high {
|
||||
&:hover {
|
||||
background-color: seagreen !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 敏感词汇验证 </template>
|
||||
<n-space vertical>
|
||||
<n-space>
|
||||
<n-tag
|
||||
:type="idx % 2 ? 'success' : 'warning'"
|
||||
:key="idx"
|
||||
v-for="(item, idx) in compData.keys"
|
||||
>{{ item }}</n-tag
|
||||
>
|
||||
</n-space>
|
||||
<n-input
|
||||
placeholder="输入需要验证的词汇文本"
|
||||
type="textarea"
|
||||
size="small"
|
||||
v-model:value="compData.text"
|
||||
:autosize="{ minRows: 3, maxRows: 5 }"
|
||||
@update:value="compData.handleUpdateText"
|
||||
/>
|
||||
<n-space>
|
||||
<n-tag
|
||||
:type="idx % 2 ? 'error' : 'info'"
|
||||
:key="idx"
|
||||
v-for="(item, idx) in compData.words"
|
||||
>{{ item }}</n-tag
|
||||
>
|
||||
</n-space>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import Mint from 'mint-filter';
|
||||
|
||||
const compData = reactive({
|
||||
keys: ['敏感词', '胡萝卜', '香蕉', '苹果'],
|
||||
text: '',
|
||||
words: [],
|
||||
});
|
||||
const mint = new Mint(compData.keys);
|
||||
compData.handleUpdateText = (value: string | [string, string]) => {
|
||||
const test = mint.filter(value);
|
||||
compData.words = test.words;
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-space vertical>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 字符拼音 </template>
|
||||
<template #header-extra> 陋室铭,唐代:刘禹锡 </template>
|
||||
<div class="pinyin">
|
||||
<template v-for="item in compData.items1">
|
||||
<div class="pinyin-item" :key="idx" v-for="(todo, idx) in item">
|
||||
<span class="py">{{ todo.py }}</span>
|
||||
<span class="hz">{{ todo.hz }}</span> </div
|
||||
><br />
|
||||
</template>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 字符拼音 </template>
|
||||
<template #header-extra> 李贺小传,李商隐〔唐代〕 </template>
|
||||
<div class="pinyin">
|
||||
<template v-for="item in compData.items2">
|
||||
<div class="pinyin-item" :key="idx" v-for="(todo, idx) in item">
|
||||
<span class="py">{{ todo.py }}</span>
|
||||
<span class="hz">{{ todo.hz }}</span> </div
|
||||
><br />
|
||||
</template>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { pinyin } from 'pinyin-pro';
|
||||
|
||||
interface WordInfo {
|
||||
hz: string;
|
||||
py: string;
|
||||
}
|
||||
|
||||
const text1 = [
|
||||
' 山不在高,有仙则名。水不在深,有龙则灵。斯是陋室,惟吾德馨。苔痕上阶绿,草色入帘青。谈笑有鸿儒,往来无白丁。可以调素琴,阅金经。无丝竹之乱耳,无案牍之劳形。南阳诸葛庐,西蜀子云亭。孔子云:何陋之有?',
|
||||
];
|
||||
const text2 = [
|
||||
' 京兆杜牧为李长吉集序,状长吉之奇甚尽,世传之。长吉姊嫁王氏者,语长吉之事尤备。',
|
||||
' 长吉细瘦,通眉,长指爪,能苦吟疾书。最先为昌黎韩愈所知。所与游者,王参元、杨敬之、权璩、崔植辈为密,每旦日出与诸公游,未尝得题然后为诗,如他人思量牵合,以及程限为意。恒从小奚奴,骑距驉,背一古破锦囊,遇有所得,即书投囊中。及暮归.太夫人使婢受囊出之,见所书多.辄曰:“是儿要当呕出心乃已尔。”上灯,与食。长吉从婢取书,研墨叠纸足成之,投他囊中。非大醉及吊丧日率如此,过亦不复省。王、杨辈时复来探取写去。长吉往往独骑往还京、洛,所至或时有著,随弃之,故沈子明家所余四卷而已。',
|
||||
' 长吉将死时,忽昼见一绯衣人,驾赤虬,持一板,书若太古篆或霹雳石文者,云当召长吉。长吉了不能读,欻下榻叩头,言:“阿㜷老且病,贺不愿去。”绯衣人笑曰:“帝成白玉楼,立召君为记。天上差乐,不苦也。”长吉独泣,边人尽见之。少之,长吉气绝。常所居窗中,勃勃有烟气,闻行车嘒管之声。太夫人急止人哭,待之如炊五斗黍许时,长吉竟死。王氏姊非能造作谓长吉者,实所见如此。',
|
||||
' 呜呼,天苍苍而高也,上果有帝耶?帝果有苑囿、宫室、观阁之玩耶?苟信然,则天之高邈,帝之尊严,亦宜有人物文采愈此世者,何独眷眷于长吉而使其不寿耶?噫,又岂世所谓才而奇者,不独地上少,即天上亦不多耶?长吉生二十七年,位不过奉礼太常,时人亦多排摈毁斥之,又岂才而奇者,帝独重之,而人反不重耶?又岂人见会胜帝耶?',
|
||||
];
|
||||
|
||||
const createHzPy = (text: string[]): WordInfo[][] => {
|
||||
const items: WordInfo[][] = [];
|
||||
text.forEach((item) => {
|
||||
const todo: WordInfo[] = [];
|
||||
for (let i = 0; i < item.length; i++) {
|
||||
const tg = item.charAt(i);
|
||||
todo.push({ hz: tg, py: pinyin(tg)[0] });
|
||||
}
|
||||
items.push(todo);
|
||||
});
|
||||
return items;
|
||||
};
|
||||
|
||||
const compData = reactive({
|
||||
items1: createHzPy(text1),
|
||||
items2: createHzPy(text2),
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.pinyin {
|
||||
&-item {
|
||||
line-height: 100%;
|
||||
width: 42px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
.py {
|
||||
clear: both;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
float: left;
|
||||
width: 42px;
|
||||
}
|
||||
.hz {
|
||||
clear: both;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
float: left;
|
||||
font-size: 16px;
|
||||
height: 36px;
|
||||
width: 42px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 时间线 </template>
|
||||
<n-space>
|
||||
<n-card title="竖向">
|
||||
<n-timeline>
|
||||
<n-timeline-item content="啊" />
|
||||
<n-timeline-item type="success" title="成功" content="哪里成功" time="2018-04-03 20:46" />
|
||||
<n-timeline-item type="error" content="哪里错误" time="2018-04-03 20:46" />
|
||||
<n-timeline-item type="warning" title="警告" content="哪里警告" time="2018-04-03 20:46" />
|
||||
<n-timeline-item
|
||||
type="info"
|
||||
title="信息"
|
||||
content="是的"
|
||||
time="2018-04-03 20:46"
|
||||
line-type="dashed"
|
||||
/>
|
||||
<n-timeline-item content="啊" />
|
||||
</n-timeline>
|
||||
</n-card>
|
||||
<n-card title="横向">
|
||||
<div style="overflow: auto">
|
||||
<n-timeline horizontal>
|
||||
<n-timeline-item content="啊" />
|
||||
<n-timeline-item
|
||||
type="success"
|
||||
title="成功"
|
||||
content="哪里成功"
|
||||
time="2018-04-03 20:46"
|
||||
/>
|
||||
<n-timeline-item type="error" content="哪里失败" time="2018-04-03 20:46" />
|
||||
<n-timeline-item
|
||||
type="warning"
|
||||
title="警告"
|
||||
content="哪里警告"
|
||||
time="2018-04-03 20:46"
|
||||
/>
|
||||
<n-timeline-item type="info" title="信息" content="是的" time="2018-04-03 20:46" />
|
||||
</n-timeline>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card title="自定义">
|
||||
<n-timeline :icon-size="20">
|
||||
<n-timeline-item color="grey" content="啊">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<cash-icon />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
<n-timeline-item type="success" title="成功" content="哪里成功" time="2018-04-03 20:46">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<cash-icon />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
<n-timeline-item type="error" content="哪里错误" time="2018-04-03 20:46">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<cash-icon />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
<n-timeline-item type="warning" title="警告" content="哪里警告" time="2018-04-03 20:46">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<cash-icon />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
<n-timeline-item type="info" title="信息" content="是的" time="2018-04-03 20:46">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<cash-icon />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
</n-timeline>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { CashOutline as CashIcon } from '@vicons/ionicons5';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
CashIcon,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<Iframe ref="iframeRef" style="zoom: 0.8" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Iframe from '../../../../iframe/index.vue';
|
||||
|
||||
const iframeRef = ref();
|
||||
|
||||
onMounted(() => {
|
||||
iframeRef.value?.open('https://www.makeapie.cn/echarts');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<Iframe ref="iframeRef" style="zoom: 0.9" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Iframe from '../../../../iframe/index.vue';
|
||||
|
||||
const iframeRef = ref();
|
||||
|
||||
onMounted(() => {
|
||||
iframeRef.value?.open('https://madeapie.com/');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<Iframe ref="iframeRef" style="zoom: 0.9" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Iframe from '../../../../iframe/index.vue';
|
||||
|
||||
const iframeRef = ref();
|
||||
|
||||
onMounted(() => {
|
||||
iframeRef.value?.open('https://ppchart.com/#/');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 瀑布流组件 </template>
|
||||
<template #header-extra>
|
||||
<n-tag type="success" @click="handleClick">vue-waterfall-plugin-next</n-tag>
|
||||
</template>
|
||||
<Waterfall
|
||||
:row-key="options.rowKey"
|
||||
:gutter="options.gutter"
|
||||
:has-around-gutter="options.hasAroundGutter"
|
||||
:width="options.width"
|
||||
:breakpoints="options.breakpoints"
|
||||
:img-selector="options.imgSelector"
|
||||
:background-color="options.backgroundColor"
|
||||
:animation-effect="options.animationEffect"
|
||||
:animation-duration="options.animationDuration"
|
||||
:animation-delay="options.animationDelay"
|
||||
:lazyload="options.lazyload"
|
||||
:list="compData.items"
|
||||
>
|
||||
<template #item="{ item }">
|
||||
<div class="waterfall-item" :style="item.style">
|
||||
<span>君问归期未有期,巴山夜雨涨秋池。何当共剪西窗烛,却话巴山夜雨时。</span>
|
||||
</div>
|
||||
</template>
|
||||
</Waterfall>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { Waterfall } from 'vue-waterfall-plugin-next';
|
||||
import 'vue-waterfall-plugin-next/dist/style.css';
|
||||
import { rdmLightRgbColor } from '@/utils/hotgo';
|
||||
|
||||
const options = reactive({
|
||||
// 唯一key值
|
||||
rowKey: 'id',
|
||||
// 卡片之间的间隙
|
||||
gutter: 20,
|
||||
// 是否有周围的gutter
|
||||
hasAroundGutter: true,
|
||||
// 卡片在PC上的宽度
|
||||
width: 250,
|
||||
// 自定义行显示个数,主要用于对移动端的适配
|
||||
breakpoints: {
|
||||
1200: {
|
||||
// 当屏幕宽度小于等于1200
|
||||
rowPerView: 5,
|
||||
},
|
||||
800: {
|
||||
// 当屏幕宽度小于等于800
|
||||
rowPerView: 3,
|
||||
},
|
||||
500: {
|
||||
// 当屏幕宽度小于等于500
|
||||
rowPerView: 1,
|
||||
},
|
||||
},
|
||||
// 动画效果
|
||||
animationEffect: 'animate__fadeInUp',
|
||||
// 动画时间
|
||||
animationDuration: 1000,
|
||||
// 动画延迟
|
||||
animationDelay: 300,
|
||||
// 背景色
|
||||
backgroundColor: '',
|
||||
// imgSelector
|
||||
imgSelector: 'src.original',
|
||||
// 是否懒加载
|
||||
lazyload: true,
|
||||
});
|
||||
|
||||
const items: any = [];
|
||||
const genBetweenRight = (m, n) => Math.floor(Math.random() * (n - m) + 1) + m;
|
||||
for (let i = 0; i < 90; i++) {
|
||||
items.push({
|
||||
style: {
|
||||
height: genBetweenRight(100, 300) + 'px',
|
||||
'background-color': rdmLightRgbColor(),
|
||||
},
|
||||
});
|
||||
}
|
||||
const compData = reactive({
|
||||
items,
|
||||
});
|
||||
const handleClick = () => {
|
||||
window.open('https://vue-waterfall.netlify.app/');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.waterfall-item {
|
||||
border: 2px solid rgb(244, 244, 248);
|
||||
height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 文字水印 </template>
|
||||
<template #header-extra> 核心机密 </template>
|
||||
<n-watermark
|
||||
content="核心机密"
|
||||
cross
|
||||
selectable
|
||||
:font-size="16"
|
||||
:line-height="16"
|
||||
:width="192"
|
||||
:height="128"
|
||||
:x-offset="12"
|
||||
:y-offset="28"
|
||||
:rotate="-15"
|
||||
>
|
||||
<n-table :single-line="false">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>复盘</th>
|
||||
<th>赋能</th>
|
||||
<th>协同</th>
|
||||
<th>...</th>
|
||||
<th>串联</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>拉通</td>
|
||||
<td>打通</td>
|
||||
<td>树立</td>
|
||||
<td>...</td>
|
||||
<td>履约</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</n-watermark>
|
||||
</n-card>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 图片水印 </template>
|
||||
<template #header-extra>
|
||||
<n-image height="30" width="30" :src="compData.img" />
|
||||
</template>
|
||||
<n-watermark
|
||||
:image="compData.img"
|
||||
cross
|
||||
:rotate="-15"
|
||||
:font-size="16"
|
||||
:line-height="16"
|
||||
:width="192"
|
||||
:height="128"
|
||||
:x-offset="12"
|
||||
:y-offset="30"
|
||||
:image-width="34"
|
||||
:image-opacity="0.24"
|
||||
>
|
||||
<n-table :single-line="false">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>复盘</th>
|
||||
<th>赋能</th>
|
||||
<th>协同</th>
|
||||
<th>...</th>
|
||||
<th>串联</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>拉通</td>
|
||||
<td>打通</td>
|
||||
<td>树立</td>
|
||||
<td>...</td>
|
||||
<td>履约</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</n-watermark>
|
||||
</n-card>
|
||||
<n-card
|
||||
:segmented="{ content: true, footer: true }"
|
||||
header-style="padding:10px"
|
||||
footer-style="padding:10px"
|
||||
>
|
||||
<template #header> 自定义水印 </template>
|
||||
<template #header-extra>
|
||||
<n-input-group>
|
||||
<n-input v-model:value="compData.text" placeholder="输入自定义水印" />
|
||||
<n-button type="primary" ghost>添加</n-button>
|
||||
</n-input-group>
|
||||
</template>
|
||||
<n-watermark
|
||||
v-model:content="compData.text"
|
||||
cross
|
||||
selectable
|
||||
:font-size="16"
|
||||
:line-height="16"
|
||||
:width="192"
|
||||
:height="128"
|
||||
:x-offset="12"
|
||||
:y-offset="28"
|
||||
:rotate="-15"
|
||||
>
|
||||
<n-table :single-line="false">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>复盘</th>
|
||||
<th>赋能</th>
|
||||
<th>协同</th>
|
||||
<th>...</th>
|
||||
<th>串联</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>拉通</td>
|
||||
<td>打通</td>
|
||||
<td>树立</td>
|
||||
<td>...</td>
|
||||
<td>履约</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</n-watermark>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive } from 'vue';
|
||||
import logo from '@/assets/images/logo.png';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const compData = reactive({
|
||||
img: logo,
|
||||
text: '自定义水印',
|
||||
});
|
||||
return {
|
||||
compData,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="show" description="请稍候...">
|
||||
<n-form :label-width="80" :model="formValue" :rules="rules" ref="formRef">
|
||||
<n-form-item label="测试参数" path="basicTest">
|
||||
<n-input v-model:value="formValue.basicTest" placeholder="请输入测试参数" />
|
||||
<template #feedback>
|
||||
这是一个测试参数,每个插件都可以有独立的配置项,可以按需添加</template
|
||||
>
|
||||
</n-form-item>
|
||||
|
||||
<div>
|
||||
<n-space>
|
||||
<n-button type="primary" @click="formSubmit">保存更新</n-button>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { getConfig, updateConfig } from '@/api/addons/hgexample/config';
|
||||
|
||||
const group = ref('basic');
|
||||
|
||||
const show = ref(false);
|
||||
const rules = {
|
||||
basicTest: {
|
||||
required: true,
|
||||
message: '请输入测试参数',
|
||||
trigger: 'blur',
|
||||
},
|
||||
};
|
||||
|
||||
const formRef: any = ref(null);
|
||||
const message = useMessage();
|
||||
|
||||
const formValue = ref({
|
||||
basicTest: 'HotGo',
|
||||
});
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
updateConfig({ group: group.value, list: formValue.value }).then((_res) => {
|
||||
message.success('更新成功');
|
||||
load();
|
||||
});
|
||||
} else {
|
||||
message.error('验证失败,请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
load();
|
||||
});
|
||||
|
||||
function load() {
|
||||
show.value = true;
|
||||
new Promise((_resolve, _reject) => {
|
||||
getConfig({ group: group.value })
|
||||
.then((res) => {
|
||||
formValue.value = res.list;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-grid cols="24 300:1 600:24" :x-gap="12">
|
||||
<n-grid-item span="6">
|
||||
<n-card :bordered="false" size="small" class="proCard">
|
||||
<n-thing
|
||||
class="thing-cell"
|
||||
v-for="item in typeTabList"
|
||||
:key="item.key"
|
||||
:class="{ 'thing-cell-on': type === item.key }"
|
||||
@click="switchType(item)"
|
||||
>
|
||||
<template #header>{{ item.name }}</template>
|
||||
<template #description>{{ item.desc }}</template>
|
||||
</n-thing>
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
<n-grid-item span="18">
|
||||
<n-card :bordered="false" size="small" :title="typeTitle" class="proCard">
|
||||
<BasicSetting v-if="type === 1" />
|
||||
</n-card>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs } from 'vue';
|
||||
import BasicSetting from './BasicSetting.vue';
|
||||
const typeTabList = [
|
||||
{
|
||||
name: '基本设置',
|
||||
desc: '系统常规设置',
|
||||
key: 1,
|
||||
},
|
||||
];
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BasicSetting,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
type: 1,
|
||||
typeTitle: '基本设置',
|
||||
});
|
||||
|
||||
function switchType(e) {
|
||||
state.type = e.key;
|
||||
state.typeTitle = e.name;
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
switchType,
|
||||
typeTabList,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.thing-cell {
|
||||
margin: 0 -16px 10px;
|
||||
padding: 5px 16px;
|
||||
|
||||
&:hover {
|
||||
background: #f3f3f3;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.thing-cell-on {
|
||||
background: #f0faff;
|
||||
color: #2d8cf0;
|
||||
|
||||
::v-deep(.n-thing-main .n-thing-header .n-thing-header__title) {
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f0faff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<n-form class="py-4">
|
||||
<n-form-item label="测试入口" path="index">
|
||||
<n-input-group>
|
||||
<n-input placeholder="请输入" :default-value="url" :disabled="true" />
|
||||
<n-button v-copy="url" type="primary" @click="copy"> 复制链接 </n-button>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="二维码" path="index" v-if="showQrcode">
|
||||
<div class="text-center">
|
||||
<qrcode-vue :value="url" :size="220" class="canvas" style="margin: 0 auto" />
|
||||
</div>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import QrcodeVue from 'qrcode.vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
const message = useMessage();
|
||||
|
||||
interface Props {
|
||||
path: string;
|
||||
showQrcode?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
path: '',
|
||||
showQrcode: true,
|
||||
});
|
||||
|
||||
const copy = () => {
|
||||
message.success('复制成功');
|
||||
};
|
||||
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
const url = useUserStore.config?.domain + props.path;
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
::v-deep(.card-tabs .n-tabs-nav--bar-type) {
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card title="应用入口" class="proCard">
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane name="admin" tab="后台API">
|
||||
<Form path="/admin/hgexample/index/test?name=HotGo" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="api" tab="前台API">
|
||||
<Form path="/api/hgexample/index/test?name=HotGo" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="home" tab="前台页面">
|
||||
<Form path="/home/hgexample/index/test?name=HotGo" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="static" tab="静态目录">
|
||||
<Form path="/addons/hgexample/default" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="websocket" tab="Websocket">
|
||||
<Form path="/socket/hgexample/index/test?name=HotGo" :showQrcode="false" />
|
||||
<WebsocketTest />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Form from './form.vue';
|
||||
import WebsocketTest from './websocketTest.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
::v-deep(.card-tabs .n-tabs-nav--bar-type) {
|
||||
padding-left: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="测试websocket">
|
||||
尝试在下方输入框中输入任意文字消息内容,发送后websocket服务器收到会原样返回
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-space vertical>
|
||||
<n-input-group style="width: 520px">
|
||||
<n-input
|
||||
@keyup.enter="sendMessage"
|
||||
:style="{ width: '78%' }"
|
||||
placeholder="请输入消息内容"
|
||||
:on-focus="onFocus"
|
||||
:on-blur="onBlur"
|
||||
v-model:value="inputMessage"
|
||||
/>
|
||||
<n-button type="primary" @click="sendMessage"> 发送消息</n-button>
|
||||
</n-input-group>
|
||||
|
||||
<div class="mt-5"></div>
|
||||
|
||||
<n-timeline :icon-size="20">
|
||||
<n-timeline-item color="grey" content="输入中.." v-if="isInput">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<MessageOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
|
||||
<n-timeline-item
|
||||
v-for="item in messages"
|
||||
:key="item"
|
||||
:type="item.type == Enum.SendType ? 'success' : 'info'"
|
||||
:title="item.type == Enum.SendType ? '发送消息' : '收到消息'"
|
||||
:content="item.content"
|
||||
:time="item.time"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<SendOutlined v-if="item.type == Enum.SendType" />
|
||||
<SoundOutlined v-if="item.type == Enum.ReceiveType" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-timeline-item>
|
||||
</n-timeline>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { MessageOutlined, SendOutlined, SoundOutlined } from '@vicons/antd';
|
||||
import { format } from 'date-fns';
|
||||
import { addOnMessage, removeOnMessage, sendMsg, WebSocketMessage } from '@/utils/websocket';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
interface Message {
|
||||
type: Enum;
|
||||
content: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
const message = useMessage();
|
||||
const messages = ref<Message[]>([]);
|
||||
const inputMessage = ref('你好,HotGo');
|
||||
const isInput = ref(false);
|
||||
const testMessageEvent = 'admin/addons/hgexample/testMessage';
|
||||
|
||||
enum Enum {
|
||||
SendType = 1, // 发送类型
|
||||
ReceiveType = 2, // 接受类型
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
isInput.value = true;
|
||||
}
|
||||
|
||||
function onBlur() {
|
||||
isInput.value = false;
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
if (inputMessage.value == '') {
|
||||
message.error('消息内容不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
sendMsg(testMessageEvent, {
|
||||
message: inputMessage.value,
|
||||
});
|
||||
|
||||
const msg: Message = {
|
||||
type: Enum.SendType,
|
||||
content: inputMessage.value,
|
||||
time: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
||||
};
|
||||
insertMessage(msg);
|
||||
inputMessage.value = '';
|
||||
}
|
||||
|
||||
// 存入本地记录
|
||||
function insertMessage(msg: Message): void {
|
||||
messages.value.unshift(msg); // 在头部插入消息
|
||||
if (messages.value.length > 10) {
|
||||
messages.value = messages.value.slice(0, 10); // 如果超过10个,则只保留最前面10个
|
||||
}
|
||||
}
|
||||
|
||||
const onMessage = (res: WebSocketMessage) => {
|
||||
const msg: Message = {
|
||||
type: Enum.ReceiveType,
|
||||
content: res.data.message,
|
||||
time: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
|
||||
};
|
||||
insertMessage(msg);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 在当前页面注册消息监听
|
||||
addOnMessage(testMessageEvent, onMessage);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 移除消息监听
|
||||
removeOnMessage(testMessageEvent);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="params?.id > 0 ? '编辑 #' + params?.id : '添加'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-form
|
||||
:model="params"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input placeholder="请输入标题" v-model:value="params.title" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="分类ID" path="categoryId">
|
||||
<n-input-number placeholder="请输入分类ID" v-model:value="params.categoryId" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="标签" path="flag">
|
||||
<n-checkbox-group v-model:value="params.flag">
|
||||
<n-space>
|
||||
<n-checkbox
|
||||
v-for="item in dict.getOptionUnRef('sys_notice_type')"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input type="textarea" placeholder="描述" v-model:value="params.description" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="内容" path="content">
|
||||
<Editor style="height: 450px" v-model:value="params.content" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="单图" path="image">
|
||||
<UploadImage v-model:value="params.image" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="多图" path="images">
|
||||
<FileChooser :maxNumber="10" v-model:value="params.images" fileType="image" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="单附件" path="attachfile">
|
||||
<FileChooser v-model:value="params.attachfile" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="多附件" path="attachfiles">
|
||||
<UploadFile :maxNumber="10" v-model:value="params.attachfiles" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="键值对" path="map">
|
||||
<n-dynamic-input
|
||||
v-model:value="params.map"
|
||||
preset="pair"
|
||||
key-placeholder="键名"
|
||||
value-placeholder="键值"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="推荐星" path="star">
|
||||
<n-rate allow-half :default-value="params.star" :on-update:value="updateStar" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="价格" path="price">
|
||||
<n-input-number
|
||||
placeholder="请输入价格"
|
||||
clearable
|
||||
v-model:value="params.price"
|
||||
:precision="2"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="活动时间" path="activityAt">
|
||||
<DatePicker v-model:formValue="params.activityAt" type="date" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="开放时间" path="startAt">
|
||||
<DatePicker
|
||||
v-model:startValue="params.startAt"
|
||||
v-model:endValue="params.endAt"
|
||||
type="datetimerange"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="用户渠道" path="channel">
|
||||
<n-select
|
||||
v-model:value="params.channel"
|
||||
:options="dict.getOptionUnRef('sys_user_channel')"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="所在城市" path="cityId">
|
||||
<CitySelector v-model:value="params.cityId" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="用户爱好" path="hobby">
|
||||
<n-select
|
||||
multiple
|
||||
v-model:value="params.hobby"
|
||||
:options="dict.getOptionUnRef('sys_user_hobby')"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="QQ" path="qq">
|
||||
<n-input placeholder="请输入QQ号" v-model:value="params.qq" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="邮箱" path="email">
|
||||
<n-input placeholder="请输入邮箱地址" v-model:value="params.email" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="手机号" path="mobile">
|
||||
<n-input placeholder="请输入手机号" v-model:value="params.mobile" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="params.sort" clearable />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="params.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in dict.getOptionUnRef('sys_normal_disable')"
|
||||
:key="Number(status.value)"
|
||||
:value="Number(status.value)"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input type="textarea" placeholder="请输入备注" v-model:value="params.remark" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { rules, State, newState } from './model';
|
||||
import { Edit, MaxSort } from '@/api/addons/hgexample/table';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import DatePicker from '@/components/DatePicker/datePicker.vue';
|
||||
import Editor from '@/components/Editor/editor.vue';
|
||||
import UploadImage from '@/components/Upload/uploadImage.vue';
|
||||
import UploadFile from '@/components/Upload/uploadFile.vue';
|
||||
import CitySelector from '@/components/CitySelector/citySelector.vue';
|
||||
import FileChooser from '@/components/FileChooser/index.vue';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
formParams?: State;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
formParams: () => {
|
||||
return newState(null);
|
||||
},
|
||||
});
|
||||
|
||||
const isShowModal = computed({
|
||||
get: () => {
|
||||
return props.showModal;
|
||||
},
|
||||
set: (value) => {
|
||||
emit('updateShowModal', value);
|
||||
},
|
||||
});
|
||||
|
||||
const params = computed(() => {
|
||||
return props.formParams;
|
||||
});
|
||||
|
||||
const dict = useDictStore();
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(params.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
isShowModal.value = false;
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function updateStar(num) {
|
||||
params.value.star = num;
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => params.value,
|
||||
(value) => {
|
||||
if (value.id === 0) {
|
||||
MaxSort().then((res) => {
|
||||
params.value.sort = res.sort;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="表格例子">
|
||||
这里提供了一些常用的普通表格组件的用法和表单组件的例子,你可能会需要
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
@update:sorter="handleUpdateSorter"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleExport" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit
|
||||
@reload-table="reloadTable"
|
||||
@update-show-modal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { useSorter } from '@/hooks/common';
|
||||
import { Delete, List, Status, Export } from '@/api/addons/hgexample/table';
|
||||
import { State, columns, schemas, newState, loadOptions } from './model';
|
||||
import { DeleteOutlined, PlusOutlined, ExportOutlined } from '@vicons/antd';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const dict = useDictStore();
|
||||
const router = useRouter();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const showModal = ref(false);
|
||||
const formParams = ref<State>();
|
||||
const actionRef = ref();
|
||||
const { updateSorter: handleUpdateSorter, sortStatesRef: sortStatesRef } = useSorter(reloadTable);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 300,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
onClick: handleStatus.bind(null, record, 2),
|
||||
ifShow: () => {
|
||||
return record.status === 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '启用',
|
||||
onClick: handleStatus.bind(null, record, 1),
|
||||
ifShow: () => {
|
||||
return record.status === 2;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
dropDownActions: [
|
||||
{
|
||||
label: '查看详情',
|
||||
key: 'view',
|
||||
},
|
||||
{
|
||||
label: '更多按钮1',
|
||||
key: 'test1',
|
||||
// 根据业务控制是否显示: 非enable状态的不显示启用按钮
|
||||
ifShow: () => {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '更多按钮2',
|
||||
key: 'test2',
|
||||
ifShow: () => {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
],
|
||||
select: (key) => {
|
||||
if (key === 'view') {
|
||||
return handleView(record);
|
||||
}
|
||||
message.info(`您点击了,${key} 按钮`);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({
|
||||
...searchFormRef.value?.formModel,
|
||||
...{ sorters: sortStatesRef.value },
|
||||
...res,
|
||||
});
|
||||
};
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
}
|
||||
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleView(record: Recordable) {
|
||||
router.push({ name: 'table_view', params: { id: record.id } });
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(record as State);
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
batchDeleteDisabled.value = true;
|
||||
checkedIds.value = [];
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
|
||||
function handleStatus(record: Recordable, status: number) {
|
||||
Status({ id: record.id, status: status }).then((_res) => {
|
||||
message.success('设为' + dict.getLabel('sys_normal_disable', status) + '成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,466 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NAvatar, NImage, NTag, NSwitch, NRate } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { Switch } from '@/api/addons/hgexample/table';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
import { fallbackSrc } from '@/utils/hotgo';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
import type { FormRules } from 'naive-ui/es/form/src/interface';
|
||||
|
||||
const dict = useDictStore();
|
||||
const $message = window['$message'];
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
memberId: number;
|
||||
categoryId: number;
|
||||
flag: number[] | null;
|
||||
title: string;
|
||||
content: string;
|
||||
image: string;
|
||||
images: string[] | null;
|
||||
attachfile: string;
|
||||
attachfiles: string[] | null;
|
||||
map: unknown[] | null;
|
||||
star: number;
|
||||
description: string;
|
||||
price: number;
|
||||
views: number;
|
||||
activityAt: string;
|
||||
startAt: null;
|
||||
endAt: null;
|
||||
switch: number;
|
||||
sort: number;
|
||||
avatar: string;
|
||||
sex: number;
|
||||
qq: string;
|
||||
email: string;
|
||||
mobile: string;
|
||||
channel: number;
|
||||
cityId: number;
|
||||
hobby: string[] | null;
|
||||
pid: number;
|
||||
level: number;
|
||||
tree: string;
|
||||
remark: string;
|
||||
status: number;
|
||||
createdBy: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
id: 0,
|
||||
memberId: 0,
|
||||
categoryId: 0,
|
||||
flag: [1],
|
||||
title: '',
|
||||
content: '',
|
||||
image: '',
|
||||
images: null,
|
||||
attachfile: '',
|
||||
attachfiles: null,
|
||||
map: null,
|
||||
star: 0,
|
||||
description: '',
|
||||
price: 0,
|
||||
views: 0,
|
||||
activityAt: '',
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
switch: 0,
|
||||
sort: 0,
|
||||
avatar: '',
|
||||
sex: 0,
|
||||
qq: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
channel: 0,
|
||||
cityId: 0,
|
||||
hobby: null,
|
||||
pid: 0,
|
||||
level: 1,
|
||||
tree: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
createdBy: 0,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
};
|
||||
|
||||
export function newState(state: State | null): State {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
export const rules: FormRules = {
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入标题',
|
||||
},
|
||||
price: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
validator: validate.amount,
|
||||
},
|
||||
qq: {
|
||||
required: false,
|
||||
trigger: ['blur', 'input'],
|
||||
validator: validate.qq,
|
||||
},
|
||||
email: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
validator: validate.email,
|
||||
},
|
||||
mobile: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
validator: validate.phone,
|
||||
},
|
||||
};
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '标题',
|
||||
componentProps: {
|
||||
placeholder: '请输入标题',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入标题', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
component: 'NInput',
|
||||
label: '内容',
|
||||
componentProps: {
|
||||
placeholder: '请输入内容关键词',
|
||||
showButton: false,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
labelMessage: '我是自定义提示',
|
||||
component: 'NInput',
|
||||
label: '价格',
|
||||
componentProps: {
|
||||
pair: true,
|
||||
separator: '-',
|
||||
clearable: true,
|
||||
placeholder: ['从', '到'],
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'activityAt',
|
||||
component: 'NDatePicker',
|
||||
label: '活动时间',
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
clearable: true,
|
||||
shortcuts: defShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'flag',
|
||||
component: 'NCheckbox',
|
||||
label: '标签',
|
||||
giProps: {
|
||||
span: 1,
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请选择标签',
|
||||
options: dict.getOption('sys_notice_type'),
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'switch',
|
||||
component: 'NRadioGroup',
|
||||
label: '开关',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
options: dict.getOption('sys_switch'),
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'hobby',
|
||||
component: 'NSelect',
|
||||
label: '爱好',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
multiple: true,
|
||||
placeholder: '请选择爱好',
|
||||
options: dict.getOption('sys_user_hobby'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: dict.getOption('sys_normal_disable'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
sorter: true, // 单列排序
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
render(row) {
|
||||
return row.title;
|
||||
},
|
||||
sorter: {
|
||||
multiple: 1, // 为多列排序的优先级,越高优先级越高
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
key: 'flag',
|
||||
render(row) {
|
||||
if (isNullObject(row.flag)) {
|
||||
return ``;
|
||||
}
|
||||
return row.flag.map((tagKey) => {
|
||||
return renderOptionTag('sys_notice_type', tagKey);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '单图',
|
||||
key: 'image',
|
||||
render(row) {
|
||||
return h(NImage, {
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: row.image,
|
||||
fallbackSrc: fallbackSrc(),
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '多图',
|
||||
key: 'images',
|
||||
render(row) {
|
||||
if (isNullObject(row.images)) {
|
||||
return ``;
|
||||
}
|
||||
return row.images.map((image) => {
|
||||
return h(NImage, {
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: image,
|
||||
fallbackSrc: fallbackSrc(),
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
'margin-left': '2px',
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '附件',
|
||||
key: 'attachfile',
|
||||
render(row) {
|
||||
if (row.attachfile === '') {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NAvatar,
|
||||
{
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
default: () => getFileExt(row.attachfile),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '多附件',
|
||||
key: 'attachfiles',
|
||||
render(row) {
|
||||
if (isNullObject(row.attachfiles)) {
|
||||
return ``;
|
||||
}
|
||||
return row.attachfiles.map((attachfile) => {
|
||||
return h(
|
||||
NAvatar,
|
||||
{
|
||||
size: 'small',
|
||||
style: {
|
||||
'margin-left': '2px',
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => getFileExt(attachfile),
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '推荐星',
|
||||
key: 'star',
|
||||
// width: 180,
|
||||
render(row) {
|
||||
return h(NRate, {
|
||||
allowHalf: true,
|
||||
readonly: true,
|
||||
defaultValue: row.star,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '价格',
|
||||
key: 'price',
|
||||
sorter: {
|
||||
multiple: 2, // 为多列排序的优先级,越高优先级越高
|
||||
},
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.price.toFixed(2),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '开关',
|
||||
key: 'switch',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.switch === 1,
|
||||
checked: '开启',
|
||||
unchecked: '关闭',
|
||||
onUpdateValue: function (e) {
|
||||
row.switch = e ? 1 : 2;
|
||||
Switch({ id: row.id, key: 'switch', value: row.switch }).then((_res) => {
|
||||
$message.success('操作成功');
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '排序',
|
||||
// key: 'sort',
|
||||
// },
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return renderOptionTag('sys_normal_disable', row.status);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '爱好',
|
||||
key: 'hobby',
|
||||
render(row) {
|
||||
if (isNullObject(row.hobby)) {
|
||||
return ``;
|
||||
}
|
||||
return row.hobby.map((tagKey) => {
|
||||
return renderOptionTag('sys_user_hobby', tagKey);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '活动时间',
|
||||
key: 'activityAt',
|
||||
render(row) {
|
||||
return formatToDate(row.activityAt);
|
||||
},
|
||||
sorter: {
|
||||
multiple: 3, // 为多列排序的优先级,越高优先级越高
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function loadOptions() {
|
||||
dict.loadOptions([
|
||||
'sys_normal_disable',
|
||||
'sys_user_sex',
|
||||
'sys_notice_type',
|
||||
'sys_switch',
|
||||
'sys_user_hobby',
|
||||
'sys_user_channel',
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="基础详情"> 基础详情,有时也用于显示只读信息。 </n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
|
||||
<n-descriptions label-placement="left" class="py-2" :column="4">
|
||||
<n-descriptions-item>
|
||||
<template #label>分类ID</template>
|
||||
{{ formValue.categoryId }}
|
||||
</n-descriptions-item>
|
||||
|
||||
<n-descriptions-item label="标签">
|
||||
<template v-for="(item, key) in formValue?.flag" :key="key">
|
||||
<n-tag
|
||||
:type="dict.getType('sys_notice_type', item)"
|
||||
size="small"
|
||||
class="min-left-space"
|
||||
>{{ dict.getLabel('sys_notice_type', item) }}</n-tag
|
||||
>
|
||||
</template>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="标题">{{ formValue.title }}</n-descriptions-item>
|
||||
<n-descriptions-item label="描述">{{ formValue.description }}</n-descriptions-item>
|
||||
<n-descriptions-item label="推荐星"
|
||||
><n-rate readonly :default-value="formValue.star"
|
||||
/></n-descriptions-item>
|
||||
<n-descriptions-item label="价格">{{ formValue.price }}</n-descriptions-item>
|
||||
<n-descriptions-item label="浏览次数">{{ formValue.views }}</n-descriptions-item>
|
||||
<n-descriptions-item label="活动时间">{{ formValue.activityAt }}</n-descriptions-item>
|
||||
<n-descriptions-item label="开关">
|
||||
<n-switch v-model:value="formValue.switch" :unchecked-value="2" :checked-value="1"
|
||||
/></n-descriptions-item>
|
||||
<n-descriptions-item label="创建人ID">{{ formValue.createdBy }} </n-descriptions-item>
|
||||
<n-descriptions-item label="创建时间">{{ formValue.createdAt }} </n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
|
||||
<n-descriptions label-placement="top" title="内容" class="py-2" :column="1">
|
||||
<n-descriptions-item><span v-html="formValue.content"></span></n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
|
||||
<n-card :bordered="false" class="proCard mt-4" size="small" :segmented="{ content: true }">
|
||||
<n-descriptions label-placement="top" title="单图" class="py-2" :column="1">
|
||||
<n-descriptions-item>
|
||||
<n-image style="margin-left: 10px; height: 100px; width: 100px" :src="formValue.image"
|
||||
/></n-descriptions-item>
|
||||
</n-descriptions>
|
||||
|
||||
<n-descriptions label-placement="top" title="多图" class="py-2" :column="1">
|
||||
<n-descriptions-item>
|
||||
<n-image-group>
|
||||
<n-space>
|
||||
<span v-for="(item, key) in formValue?.images" :key="key">
|
||||
<n-image style="margin-left: 10px; height: 100px; width: 100px" :src="item" />
|
||||
</span>
|
||||
</n-space>
|
||||
</n-image-group>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
|
||||
<n-descriptions label-placement="top" title="附件" class="py-2" :column="1">
|
||||
<n-descriptions-item>
|
||||
<div
|
||||
class="upload-card"
|
||||
v-show="formValue.attachfile !== ''"
|
||||
@click="download(formValue.attachfile)"
|
||||
>
|
||||
<div class="upload-card-item" style="height: 100px; width: 100px">
|
||||
<div class="upload-card-item-info">
|
||||
<div class="img-box">
|
||||
<n-avatar :style="fileAvatarCSS">{{ getFileExt(formValue.attachfile) }}</n-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
|
||||
<n-descriptions label-placement="top" title="多附件" class="py-2" :column="1">
|
||||
<n-descriptions-item>
|
||||
<div class="upload-card">
|
||||
<n-space style="gap: 0px 0px">
|
||||
<div
|
||||
class="upload-card-item"
|
||||
style="height: 100px; width: 100px"
|
||||
v-for="(item, key) in formValue.attachfiles"
|
||||
:key="key"
|
||||
>
|
||||
<div class="upload-card-item-info">
|
||||
<div class="img-box">
|
||||
<n-avatar :style="fileAvatarCSS" @click="download(item)">{{
|
||||
getFileExt(item)
|
||||
}}</n-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</n-space>
|
||||
</div>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { View } from '@/api/addons/hgexample/table';
|
||||
import { newState } from './model';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const dict = useDictStore();
|
||||
const message = useMessage();
|
||||
const router = useRouter();
|
||||
const id = Number(router.currentRoute.value.params.id);
|
||||
const formValue = ref(newState(null));
|
||||
const fileAvatarCSS = computed(() => {
|
||||
return {
|
||||
'--n-merged-size': `var(--n-avatar-size-override, 80px)`,
|
||||
'--n-font-size': `18px`,
|
||||
};
|
||||
});
|
||||
|
||||
//下载
|
||||
function download(url: string) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (id < 1) {
|
||||
message.error('ID不正确,请检查!');
|
||||
return;
|
||||
}
|
||||
formValue.value = await View({ id: id });
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<n-alert :show-icon="false" title="说明">
|
||||
<n-p
|
||||
>这里主要演示多租户业务下,不同用户身份如何在同一页面下展示不同的表格功能和字段数据,以及添加/编辑购买订单时服务端如何自动维护多租户关系</n-p
|
||||
>
|
||||
<n-p style="font-weight: 600">不同身份的测试账号</n-p>
|
||||
<n-table :bordered="false" :single-line="false" size="small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="table-center">身份</th>
|
||||
<th class="table-center">ID</th>
|
||||
<th class="table-center">账号</th>
|
||||
<th class="table-center">密码</th>
|
||||
<th>身份描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="account in accounts" :key="account.id">
|
||||
<td class="table-center">{{ account.type }}</td>
|
||||
<td class="table-center">{{ account.id }}</td>
|
||||
<td class="table-center">{{ account.username }}</td>
|
||||
<td class="table-center">{{ account.password }}</td>
|
||||
<td>{{ account.dc }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</n-alert>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Account {
|
||||
type: string;
|
||||
id: number;
|
||||
username: string;
|
||||
password: string;
|
||||
dc: string;
|
||||
}
|
||||
|
||||
const accounts: Account[] = [
|
||||
{
|
||||
type: '公司',
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
password: '123456',
|
||||
dc: '可见全部数据。管理整个平台,包括商户和用户账户',
|
||||
},
|
||||
{
|
||||
type: '租户',
|
||||
id: 8,
|
||||
username: 'ameng',
|
||||
password: '123456',
|
||||
dc: '可见自己下面的商户和用户数据。多租户系统中顶层实体,有自己的多个商户、用户、产品、订单等',
|
||||
},
|
||||
{
|
||||
type: '商户',
|
||||
id: 11,
|
||||
username: 'abai',
|
||||
password: '123456',
|
||||
dc: '可见自己下面的用户数据。受租户的监管和管理,可以独立经营的实体,提供产品或服务,管理自己的业务,包括库存管理、订单处理、结算等',
|
||||
},
|
||||
{
|
||||
type: '用户',
|
||||
id: 12,
|
||||
username: 'asong',
|
||||
password: '123456',
|
||||
dc: '只能看到自己数据。真正购买产品或享受服务的人,与商户互动,管理个人信息等个性化功能',
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.table-center {
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
:title="formValue.id > 0 ? '编辑购买订单 #' + formValue.id : '添加购买订单'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
:label-placement="settingStore.isMobile ? 'top' : 'left'"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-grid cols="1 s:1 m:1 l:1 xl:1 2xl:1" responsive="screen">
|
||||
<n-gi span="1" v-if="userStore.isCompanyDept">
|
||||
<n-form-item label="租户ID" path="tenantId">
|
||||
<n-input placeholder="请输入租户ID" v-model:value="formValue.tenantId" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1" v-if="userStore.isCompanyDept || userStore.isTenantDept">
|
||||
<n-form-item label="商户ID" path="merchantId">
|
||||
<n-input placeholder="请输入商户ID" v-model:value="formValue.merchantId" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi
|
||||
span="1"
|
||||
v-if="userStore.isCompanyDept || userStore.isTenantDept || userStore.isMerchantDept"
|
||||
>
|
||||
<n-form-item label="用户ID" path="userId">
|
||||
<n-input placeholder="请输入用户ID" v-model:value="formValue.userId" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="购买产品" path="productName">
|
||||
<n-input placeholder="请输入购买产品" v-model:value="formValue.productName" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="关联订单号" path="orderSn">
|
||||
<n-input placeholder="请输入关联订单号" v-model:value="formValue.orderSn" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="充值金额" path="money">
|
||||
<n-input-group>
|
||||
<n-input-number
|
||||
:min="1"
|
||||
:show-button="false"
|
||||
style="width: 100%"
|
||||
placeholder="请输入充值金额"
|
||||
v-model:value="formValue.money"
|
||||
/>
|
||||
<n-input-group-label>元</n-input-group-label>
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input placeholder="请输入备注" v-model:value="formValue.remark" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="支付状态" path="status">
|
||||
<n-select
|
||||
v-model:value="formValue.status"
|
||||
:options="dict.getOptionUnRef('payStatus')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</n-scrollbar>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm"> 取消 </n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm"> 确定 </n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { Edit, View } from '@/api/addons/hgexample/tenantOrder';
|
||||
import { State, newState, rules } from './model';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const dict = useDictStore();
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const userStore = useUserStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(840);
|
||||
});
|
||||
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(formValue.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
closeForm();
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="多租户功能演示">
|
||||
<Alert />
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
ref="searchFormRef"
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
<BasicTable
|
||||
ref="actionRef"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="addTable"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/hgexample/tenantOrder/edit'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加购买订单
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit ref="editRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, computed, onMounted } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { List, Delete } from '@/api/addons/hgexample/tenantOrder';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { columns, schemas, loadOptions } from './model';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
import Alert from './alert.vue';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const searchFormRef = ref<any>({});
|
||||
const editRef = ref();
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 144,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['/hgexample/tenantOrder/edit'],
|
||||
},
|
||||
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/hgexample/tenantOrder/delete'],
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
// 加载表格数据
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
// 重新加载表格数据
|
||||
function reloadTable() {
|
||||
actionRef.value?.reload();
|
||||
}
|
||||
|
||||
// 添加数据
|
||||
function addTable() {
|
||||
editRef.value.openModal(null);
|
||||
}
|
||||
|
||||
// 编辑数据
|
||||
function handleEdit(record: Recordable) {
|
||||
editRef.value.openModal(record);
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,212 @@
|
||||
import { ref } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
import type { FormRules } from 'naive-ui/es/form/src/interface';
|
||||
|
||||
const dict = useDictStore();
|
||||
const userStore = useUserStore();
|
||||
|
||||
export class State {
|
||||
public id = 0; // 主键
|
||||
public tenantId = null; // 租户ID
|
||||
public merchantId = null; // 商户ID
|
||||
public userId = null; // 用户ID
|
||||
public productName = ''; // 购买产品
|
||||
public orderSn = ''; // 关联订单号
|
||||
public money = null; // 充值金额
|
||||
public remark = ''; // 备注
|
||||
public status = 1; // 订单状态
|
||||
public createdAt = ''; // 创建时间
|
||||
public updatedAt = ''; // 修改时间
|
||||
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
if (state instanceof State) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return new State(state);
|
||||
}
|
||||
return new State();
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const rules: FormRules = {
|
||||
money: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'number',
|
||||
message: '请输入充值金额',
|
||||
},
|
||||
};
|
||||
|
||||
// 表格搜索表单
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'tenantId',
|
||||
component: 'NInput',
|
||||
label: '租户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入租户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'merchantId',
|
||||
component: 'NInput',
|
||||
label: '商户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入商户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept || userStore.isTenantDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'userId',
|
||||
component: 'NInput',
|
||||
label: '用户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept || userStore.isTenantDept || userStore.isMerchantDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'orderSn',
|
||||
component: 'NInput',
|
||||
label: '订单号',
|
||||
componentProps: {
|
||||
placeholder: '请输入订单号',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '订单状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择订单状态',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 表格列
|
||||
export const columns = [
|
||||
{
|
||||
title: '订单ID',
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '租户ID',
|
||||
key: 'tenantId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '商户ID',
|
||||
key: 'merchantId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept || userStore.isTenantDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
ifShow: () => {
|
||||
return userStore.isCompanyDept || userStore.isTenantDept || userStore.isMerchantDept;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '购买产品',
|
||||
key: 'productName',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '订单号',
|
||||
key: 'orderSn',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '充值金额',
|
||||
key: 'money',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return row.money + ' 元';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '订单状态',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return renderOptionTag('payStatus', row.status);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['payStatus']);
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="params?.id > 0 ? '编辑 #' + params?.id : '添加'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-form
|
||||
:model="params"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="上级" path="pid">
|
||||
<n-tree-select
|
||||
:options="optionTreeData"
|
||||
:default-value="params.pid"
|
||||
v-model:value="params.pid"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input placeholder="请输入标题" v-model:value="params.title" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="分类ID" path="categoryId">
|
||||
<n-input-number placeholder="请输入分类ID" v-model:value="params.categoryId" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="标签" path="flag">
|
||||
<n-checkbox-group v-model:value="params.flag">
|
||||
<n-space>
|
||||
<n-checkbox
|
||||
v-for="item in dict.getOptionUnRef('sys_notice_type')"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input type="textarea" placeholder="描述" v-model:value="params.description" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="内容" path="content">
|
||||
<Editor style="height: 450px" v-model:value="params.content" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="单图" path="image">
|
||||
<UploadImage v-model:value="params.image" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="多图" path="images">
|
||||
<FileChooser :maxNumber="10" v-model:value="params.images" fileType="image" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="单附件" path="attachfile">
|
||||
<FileChooser v-model:value="params.attachfile" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="多附件" path="attachfiles">
|
||||
<UploadFile :maxNumber="10" v-model:value="params.attachfiles" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="键值对" path="map">
|
||||
<n-dynamic-input
|
||||
v-model:value="params.map"
|
||||
preset="pair"
|
||||
key-placeholder="键名"
|
||||
value-placeholder="键值"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="推荐星" path="star">
|
||||
<n-rate allow-half :default-value="params.star" :on-update:value="updateStar" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="价格" path="price">
|
||||
<n-input-number
|
||||
placeholder="请输入价格"
|
||||
clearable
|
||||
v-model:value="params.price"
|
||||
:precision="2"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="活动时间" path="activityAt">
|
||||
<DatePicker v-model:formValue="params.activityAt" type="date" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="开放时间" path="startAt">
|
||||
<DatePicker
|
||||
v-model:startValue="params.startAt"
|
||||
v-model:endValue="params.endAt"
|
||||
type="datetimerange"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="用户渠道" path="channel">
|
||||
<n-select
|
||||
v-model:value="params.channel"
|
||||
:options="dict.getOptionUnRef('sys_user_channel')"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="所在城市" path="cityId">
|
||||
<CitySelector v-model:value="params.cityId" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="用户爱好" path="hobby">
|
||||
<n-select
|
||||
multiple
|
||||
v-model:value="params.hobby"
|
||||
:options="dict.getOptionUnRef('sys_user_hobby')"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="QQ" path="qq">
|
||||
<n-input placeholder="请输入QQ号" v-model:value="params.qq" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="邮箱" path="email">
|
||||
<n-input placeholder="请输入邮箱地址" v-model:value="params.email" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="手机号" path="mobile">
|
||||
<n-input placeholder="请输入手机号" v-model:value="params.mobile" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="params.sort" clearable />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="params.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in dict.getOptionUnRef('sys_normal_disable')"
|
||||
:key="Number(status.value)"
|
||||
:value="Number(status.value)"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input type="textarea" placeholder="请输入备注" v-model:value="params.remark" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { rules, State, newState } from './model';
|
||||
import { Edit, MaxSort } from '@/api/addons/hgexample/treeTable';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import DatePicker from '@/components/DatePicker/datePicker.vue';
|
||||
import Editor from '@/components/Editor/editor.vue';
|
||||
import UploadImage from '@/components/Upload/uploadImage.vue';
|
||||
import UploadFile from '@/components/Upload/uploadFile.vue';
|
||||
import CitySelector from '@/components/CitySelector/citySelector.vue';
|
||||
import FileChooser from '@/components/FileChooser/index.vue';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
formParams?: State;
|
||||
optionTreeData: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
formParams: () => {
|
||||
return newState(null);
|
||||
},
|
||||
optionTreeData: [],
|
||||
});
|
||||
|
||||
const isShowModal = computed({
|
||||
get: () => {
|
||||
return props.showModal;
|
||||
},
|
||||
set: (value) => {
|
||||
emit('updateShowModal', value);
|
||||
},
|
||||
});
|
||||
|
||||
const params = computed(() => {
|
||||
return props.formParams;
|
||||
});
|
||||
|
||||
const dict = useDictStore();
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(params.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
isShowModal.value = false;
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function updateStar(num) {
|
||||
params.value.star = num;
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => params.value,
|
||||
(value) => {
|
||||
if (value.id === 0) {
|
||||
MaxSort().then((res) => {
|
||||
params.value.sort = res.sort;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="表格例子"> 一个树形表格例子 </n-card>
|
||||
</div>
|
||||
<n-grid cols="1 s:1 m:1 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12">
|
||||
<n-gi span="1">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small" class="proCard">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-button type="info" icon-placement="left" @click="openCreateDrawer">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="info"
|
||||
icon-placement="left"
|
||||
@click="openEditDrawer"
|
||||
:disabled="!formParams?.id || formParams?.id < 1"
|
||||
>
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<EditOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
编辑
|
||||
</n-button>
|
||||
<n-button type="error" icon-placement="left" @click="handleDel">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
删除
|
||||
</n-button>
|
||||
<n-button type="info" icon-placement="left" @click="packHandle">
|
||||
{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<AlignLeftOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<n-input v-model:value="pattern" placeholder="输入地区名称搜索">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
<div class="py-3 menu-list">
|
||||
<template v-if="loading">
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<n-spin size="medium" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-tree
|
||||
block-line
|
||||
cascade
|
||||
checkable
|
||||
:virtual-scroll="true"
|
||||
:pattern="pattern"
|
||||
:data="treeData"
|
||||
:expandedKeys="expandedKeys"
|
||||
style="height: 75vh"
|
||||
@update:selected-keys="selectedTree"
|
||||
@update:expanded-keys="onExpandedKeys"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi span="3">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small" class="proCard">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-icon size="18">
|
||||
<FormOutlined />
|
||||
</n-icon>
|
||||
<span>正在编辑 {{ treeItemTitle ? `:${treeItemTitle}` : '' }}</span>
|
||||
<span style="font-size: 14px">{{
|
||||
treeItemTitle ? '' : '从列表选择一项后,进行编辑'
|
||||
}}</span>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<List
|
||||
:checkedId="checkedId"
|
||||
:optionTreeData="optionTreeData"
|
||||
@reload-table="reloadTable"
|
||||
/>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<Edit
|
||||
@reload-table="reloadTable"
|
||||
@update-show-modal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
:optionTreeData="optionTreeData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, unref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import {
|
||||
AlignLeftOutlined,
|
||||
FormOutlined,
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@vicons/antd';
|
||||
import List from './list.vue';
|
||||
import { Delete, Select } from '@/api/addons/hgexample/treeTable';
|
||||
import Edit from './edit.vue';
|
||||
import { newState, loadOptions } from './model';
|
||||
|
||||
const showModal = ref(false);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
let treeItemKey = ref([]);
|
||||
let expandedKeys = ref([]);
|
||||
const treeData = ref([]);
|
||||
const loading = ref(true);
|
||||
const treeItemTitle = ref('');
|
||||
const checkedId = ref(0);
|
||||
const pattern = ref('');
|
||||
const optionTreeData = ref<any>([]);
|
||||
const formParams = ref(newState(null));
|
||||
|
||||
function openCreateDrawer() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
}
|
||||
|
||||
function openEditDrawer() {
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
function selectedTree(keys, opts) {
|
||||
if (keys.length) {
|
||||
const treeItem = opts[0];
|
||||
treeItemKey.value = keys;
|
||||
treeItemTitle.value = treeItem.label;
|
||||
formParams.value = newState(treeItem);
|
||||
checkedId.value = treeItem.id;
|
||||
} else {
|
||||
treeItemKey.value = [];
|
||||
treeItemTitle.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function handleDel() {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: `您确定想删除吗?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ ...formParams.value }).then(async (_res) => {
|
||||
message.success('操作成功');
|
||||
await reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
message.error('已取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function packHandle() {
|
||||
if (expandedKeys.value.length) {
|
||||
expandedKeys.value = [];
|
||||
} else {
|
||||
expandedKeys.value = unref(treeData).map((item: any) => item.key as string) as [];
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await reloadTable();
|
||||
});
|
||||
|
||||
async function reloadTable() {
|
||||
const treeMenuList = await Select();
|
||||
Object.assign(
|
||||
formParams,
|
||||
treeMenuList.list.map((item) => item.key)
|
||||
);
|
||||
treeData.value = [];
|
||||
optionTreeData.value = [
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '根目录',
|
||||
pid: 0,
|
||||
title: '根目录',
|
||||
},
|
||||
];
|
||||
treeData.value = treeMenuList.list;
|
||||
optionTreeData.value = optionTreeData.value.concat(treeMenuList.list);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function onExpandedKeys(keys) {
|
||||
expandedKeys.value = keys;
|
||||
}
|
||||
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-result
|
||||
v-show="checkedId <= 0"
|
||||
status="info"
|
||||
title="提示"
|
||||
description="请选择一个想要编辑的树"
|
||||
/>
|
||||
|
||||
<div v-show="checkedId > 0">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
@update:sorter="handleUpdateSorter"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleExport" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<Edit
|
||||
@reload-table="reloadTable"
|
||||
@update-show-modal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
:optionTreeData="optionTreeData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, reactive, ref, watch } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { useSorter } from '@/hooks/common';
|
||||
import { Delete, List, Status, Export } from '@/api/addons/hgexample/treeTable';
|
||||
import { State, columns, schemas, newState } from './model';
|
||||
import { DeleteOutlined, PlusOutlined, ExportOutlined } from '@vicons/antd';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
interface Props {
|
||||
checkedId?: number;
|
||||
optionTreeData: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { checkedId: 0, optionTreeData: [] });
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const dict = useDictStore();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const showModal = ref(false);
|
||||
const formParams = ref<State>();
|
||||
const actionRef = ref();
|
||||
const { updateSorter: handleUpdateSorter, sortStatesRef: sortStatesRef } = useSorter(reloadTable);
|
||||
const pid = ref(0);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 200,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '禁用',
|
||||
onClick: handleStatus.bind(null, record, 2),
|
||||
ifShow: () => {
|
||||
return record.status === 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '启用',
|
||||
onClick: handleStatus.bind(null, record, 1),
|
||||
ifShow: () => {
|
||||
return record.status === 2;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
if (pid.value <= 0) {
|
||||
return [];
|
||||
}
|
||||
return await List({
|
||||
...searchFormRef.value?.formModel,
|
||||
...{ sorters: sortStatesRef.value },
|
||||
...{ pid: pid.value },
|
||||
...res,
|
||||
});
|
||||
};
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
formParams.value.pid = pid.value;
|
||||
}
|
||||
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
emit('reloadTable');
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(record as State);
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
batchDeleteDisabled.value = true;
|
||||
checkedIds.value = [];
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
|
||||
function handleStatus(record: Recordable, status: number) {
|
||||
Status({ id: record.id, status: status }).then((_res) => {
|
||||
message.success('设为' + dict.getLabel('sys_normal_disable', status) + '成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
watch(props, (_newVal, _oldVal) => {
|
||||
if (pid.value === _newVal.checkedId) {
|
||||
return;
|
||||
}
|
||||
|
||||
pid.value = Number(_newVal.checkedId);
|
||||
if (_newVal.checkedId > 0) {
|
||||
reloadTable();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,460 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NAvatar, NImage, NTag, NSwitch, NRate } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { Switch } from '@/api/addons/hgexample/table';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { defRangeShortcuts, defShortcuts, formatToDate } from '@/utils/dateUtil';
|
||||
import { validate } from '@/utils/validateUtil';
|
||||
import { fallbackSrc } from '@/utils/hotgo';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
|
||||
const dict = useDictStore();
|
||||
const $message = window['$message'];
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
memberId: number;
|
||||
categoryId: number;
|
||||
flag: number[] | null;
|
||||
title: string;
|
||||
content: string;
|
||||
image: string;
|
||||
images: string[] | null;
|
||||
attachfile: string;
|
||||
attachfiles: string[] | null;
|
||||
map: any | null;
|
||||
star: number;
|
||||
description: string;
|
||||
price: number;
|
||||
views: number;
|
||||
activityAt: string;
|
||||
startAt: null;
|
||||
endAt: null;
|
||||
switch: number;
|
||||
sort: number;
|
||||
avatar: string;
|
||||
sex: number;
|
||||
qq: string;
|
||||
email: string;
|
||||
mobile: string;
|
||||
channel: number;
|
||||
cityId: number;
|
||||
hobby: string[] | null;
|
||||
pid: number;
|
||||
level: number;
|
||||
tree: string;
|
||||
remark: string;
|
||||
status: number;
|
||||
createdBy: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
id: 0,
|
||||
memberId: 0,
|
||||
categoryId: 0,
|
||||
flag: [1],
|
||||
title: '',
|
||||
content: '',
|
||||
image: '',
|
||||
images: null,
|
||||
attachfile: '',
|
||||
attachfiles: null,
|
||||
map: null,
|
||||
star: 0,
|
||||
description: '',
|
||||
price: 0,
|
||||
views: 0,
|
||||
activityAt: '',
|
||||
startAt: null,
|
||||
endAt: null,
|
||||
switch: 0,
|
||||
sort: 0,
|
||||
avatar: '',
|
||||
sex: 0,
|
||||
qq: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
channel: 0,
|
||||
cityId: 0,
|
||||
hobby: null,
|
||||
pid: 0,
|
||||
level: 1,
|
||||
tree: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
createdBy: 0,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
};
|
||||
|
||||
export function newState(state: State | null): State {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
export const rules = {
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入标题',
|
||||
},
|
||||
price: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
validator: validate.amount,
|
||||
},
|
||||
qq: {
|
||||
required: false,
|
||||
trigger: ['blur', 'input'],
|
||||
validator: validate.qq,
|
||||
},
|
||||
email: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
validator: validate.email,
|
||||
},
|
||||
mobile: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
validator: validate.phone,
|
||||
},
|
||||
};
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '标题',
|
||||
componentProps: {
|
||||
placeholder: '请输入标题',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入标题', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
component: 'NInput',
|
||||
label: '内容',
|
||||
componentProps: {
|
||||
placeholder: '请输入内容关键词',
|
||||
showButton: false,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
labelMessage: '我是自定义提示',
|
||||
component: 'NInput',
|
||||
label: '价格',
|
||||
componentProps: {
|
||||
pair: true,
|
||||
separator: '-',
|
||||
clearable: true,
|
||||
placeholder: ['从', '到'],
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'activityAt',
|
||||
component: 'NDatePicker',
|
||||
label: '活动时间',
|
||||
componentProps: {
|
||||
type: 'date',
|
||||
clearable: true,
|
||||
shortcuts: defShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'flag',
|
||||
component: 'NCheckbox',
|
||||
label: '标签',
|
||||
giProps: {
|
||||
span: 1,
|
||||
},
|
||||
componentProps: {
|
||||
placeholder: '请选择标签',
|
||||
options: dict.getOption('sys_notice_type'),
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'switch',
|
||||
component: 'NRadioGroup',
|
||||
label: '开关',
|
||||
giProps: {
|
||||
//span: 24,
|
||||
},
|
||||
componentProps: {
|
||||
options: dict.getOption('sys_switch'),
|
||||
onUpdateChecked: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'hobby',
|
||||
component: 'NSelect',
|
||||
label: '爱好',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
multiple: true,
|
||||
placeholder: '请选择爱好',
|
||||
options: dict.getOption('sys_user_hobby'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: dict.getOption('sys_normal_disable'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
sorter: true, // 单列排序
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
render(row) {
|
||||
return row.title;
|
||||
},
|
||||
sorter: {
|
||||
multiple: 1, // 为多列排序的优先级,越高优先级越高
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
key: 'flag',
|
||||
render(row) {
|
||||
if (isNullObject(row.flag)) {
|
||||
return ``;
|
||||
}
|
||||
return row.flag.map((tagKey) => {
|
||||
return renderOptionTag('sys_notice_type', tagKey);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '单图',
|
||||
key: 'image',
|
||||
render(row) {
|
||||
return h(NImage, {
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: row.image,
|
||||
fallbackSrc: fallbackSrc(),
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '多图',
|
||||
key: 'images',
|
||||
render(row) {
|
||||
if (isNullObject(row.images)) {
|
||||
return ``;
|
||||
}
|
||||
return row.images.map((image) => {
|
||||
return h(NImage, {
|
||||
width: 32,
|
||||
height: 32,
|
||||
src: image,
|
||||
fallbackSrc: fallbackSrc(),
|
||||
style: {
|
||||
width: '32px',
|
||||
height: '32px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
'margin-left': '2px',
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '附件',
|
||||
key: 'attachfile',
|
||||
render(row) {
|
||||
if (row.attachfile === '') {
|
||||
return ``;
|
||||
}
|
||||
return h(
|
||||
NAvatar,
|
||||
{
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
default: () => getFileExt(row.attachfile),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '多附件',
|
||||
key: 'attachfiles',
|
||||
render(row) {
|
||||
if (isNullObject(row.attachfiles)) {
|
||||
return ``;
|
||||
}
|
||||
return row.attachfiles.map((attachfile) => {
|
||||
return h(
|
||||
NAvatar,
|
||||
{
|
||||
size: 'small',
|
||||
style: {
|
||||
'margin-left': '2px',
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => getFileExt(attachfile),
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '推荐星',
|
||||
key: 'star',
|
||||
// width: 180,
|
||||
render(row) {
|
||||
return h(NRate, {
|
||||
allowHalf: true,
|
||||
readonly: true,
|
||||
defaultValue: row.star,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description',
|
||||
},
|
||||
{
|
||||
title: '价格',
|
||||
key: 'price',
|
||||
sorter: {
|
||||
multiple: 2, // 为多列排序的优先级,越高优先级越高
|
||||
},
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.price.toFixed(2),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '开关',
|
||||
key: 'switch',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSwitch, {
|
||||
value: row.switch === 1,
|
||||
checked: '开启',
|
||||
unchecked: '关闭',
|
||||
onUpdateValue: function (e) {
|
||||
row.switch = e ? 1 : 2;
|
||||
Switch({ id: row.id, key: 'switch', value: row.switch }).then((_res) => {
|
||||
$message.success('操作成功');
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: '排序',
|
||||
// key: 'sort',
|
||||
// },
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return renderOptionTag('sys_normal_disable', row.status);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '爱好',
|
||||
key: 'hobby',
|
||||
render(row) {
|
||||
return renderOptionTag('sys_user_hobby', row.hobby);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '活动时间',
|
||||
key: 'activityAt',
|
||||
render(row) {
|
||||
return formatToDate(row.activityAt);
|
||||
},
|
||||
sorter: {
|
||||
multiple: 3, // 为多列排序的优先级,越高优先级越高
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function loadOptions() {
|
||||
dict.loadOptions([
|
||||
'sys_normal_disable',
|
||||
'sys_user_sex',
|
||||
'sys_notice_type',
|
||||
'sys_switch',
|
||||
'sys_user_hobby',
|
||||
'sys_user_channel',
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NAvatar, NImage } from 'naive-ui';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { fallbackSrc } from '@/utils/hotgo';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'drive',
|
||||
component: 'NSelect',
|
||||
label: '上传驱动',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择上传驱动',
|
||||
options: dict.getOption('config_upload_drive'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'kind',
|
||||
component: 'NSelect',
|
||||
label: '上传类型',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择上传类型',
|
||||
options: dict.getOption('AttachmentKindOption'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
label: '文件名称',
|
||||
labelMessage: '支持模糊查询',
|
||||
componentProps: {
|
||||
placeholder: '请输入文件名称',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入文件名称', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
component: 'NDatePicker',
|
||||
label: '上传时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'member_id',
|
||||
component: 'NInput',
|
||||
label: '用户ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入用户ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入用户ID', trigger: ['blur'] }],
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '附件ID',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '应用',
|
||||
key: 'appId',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '驱动',
|
||||
key: 'drive',
|
||||
render(row) {
|
||||
return row.drive;
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '上传类型',
|
||||
key: 'kind',
|
||||
render(row) {
|
||||
return renderOptionTag('AttachmentKindOption', row.kind);
|
||||
},
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '文件名称',
|
||||
key: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '文件',
|
||||
key: 'fileUrl',
|
||||
width: 80,
|
||||
render(row) {
|
||||
if (row.fileUrl === '') {
|
||||
return ``;
|
||||
}
|
||||
if (row.kind !== 'image') {
|
||||
return h(
|
||||
NAvatar,
|
||||
{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
},
|
||||
{
|
||||
default: () => getFileExt(row.fileUrl),
|
||||
}
|
||||
);
|
||||
}
|
||||
return h(NImage, {
|
||||
width: 40,
|
||||
height: 40,
|
||||
src: row.fileUrl,
|
||||
fallbackSrc: fallbackSrc(),
|
||||
style: {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '文件大小',
|
||||
key: 'sizeFormat',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '扩展名',
|
||||
key: 'ext',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '扩展类型',
|
||||
key: 'mimeType',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '上传时间',
|
||||
key: 'updatedAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['AttachmentKindOption', 'config_upload_drive']);
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard" title="附件管理">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
@keyup.enter="handleSubmit"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="handleUpload" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<UploadOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
上传文件
|
||||
</n-button>
|
||||
<n-button type="success" @click="handleMultipartUpload" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FileAddOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
上传大文件
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleUploadImage" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FileImageOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
上传图片
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleUploadDoc" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FileWordOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
上传文档
|
||||
</n-button>
|
||||
<n-button type="success" @click="showUrlModal" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<FileImageOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
链接转图片
|
||||
</n-button>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
|
||||
<FileUpload ref="fileUploadRef" :finish-call="handleFinishCall" />
|
||||
<FileUpload ref="imageUploadRef" :finish-call="handleFinishCall" upload-type="image" />
|
||||
<FileUpload ref="docUploadRef" :finish-call="handleFinishCall" upload-type="doc" />
|
||||
<MultipartUpload ref="multipartUploadRef" @on-finish="handleFinishCall" />
|
||||
<UrlModal ref="urlModalRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { Delete, List } from '@/api/apply/attachment';
|
||||
import { columns, schemas, loadOptions } from './columns';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
UploadOutlined,
|
||||
FileWordOutlined,
|
||||
FileImageOutlined,
|
||||
FileAddOutlined,
|
||||
} from '@vicons/antd';
|
||||
import FileUpload from '@/components/FileChooser/src/Upload.vue';
|
||||
import MultipartUpload from '@/components/Upload/multipartUpload.vue';
|
||||
import { Attachment } from '@/components/FileChooser/src/model';
|
||||
import UrlModal from './urlModal.vue';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const searchFormRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const fileUploadRef = ref();
|
||||
const imageUploadRef = ref();
|
||||
const docUploadRef = ref();
|
||||
const multipartUploadRef = ref();
|
||||
const urlModalRef =ref();
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 132,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '下载',
|
||||
onClick: handleDown.bind(null, record),
|
||||
type: 'default',
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 100,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function handleUpload() {
|
||||
fileUploadRef.value.openModal();
|
||||
}
|
||||
|
||||
function handleUploadImage() {
|
||||
imageUploadRef.value.openModal();
|
||||
}
|
||||
|
||||
function handleUploadDoc() {
|
||||
docUploadRef.value.openModal();
|
||||
}
|
||||
|
||||
function handleMultipartUpload() {
|
||||
multipartUploadRef.value.openModal();
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleDown(record: Recordable) {
|
||||
window.open(record.fileUrl);
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleFinishCall(result: Attachment, success: boolean) {
|
||||
if (success) {
|
||||
reloadTable();
|
||||
}
|
||||
}
|
||||
|
||||
function showUrlModal() {
|
||||
urlModalRef.value?.showModal();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="链接转图片"
|
||||
>
|
||||
<n-alert type="info"> 将外部图片链接下载并转存至平台的存储驱动中 </n-alert>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
ref="formPacketRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form-item label="图片链接" path="url">
|
||||
<n-input v-model:value="formParams.url" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="预览图片" v-if="formParams.url != ''">
|
||||
<n-image width="150" :src="formParams.url" />
|
||||
</n-form-item>
|
||||
</n-spin>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">关闭</n-button>
|
||||
<n-button type="primary" :loading="formBtnLoading" @click="confirmForm">上传 </n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { ImageTransferStorage } from '@/api/apply/attachment';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const loading = ref(false);
|
||||
const isShowModal = ref(false);
|
||||
const dialogWidth = ref(adaModalWidth(640));
|
||||
const formBtnLoading = ref(false);
|
||||
const formPacketRef = ref();
|
||||
const message = useMessage();
|
||||
const formParams = ref({
|
||||
url: '',
|
||||
});
|
||||
|
||||
function reloadTable() {
|
||||
emit('reloadTable');
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
ImageTransferStorage(formParams.value)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
closeForm();
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
function showModal() {
|
||||
isShowModal.value = true;
|
||||
formParams.value.url = '';
|
||||
}
|
||||
|
||||
defineExpose({ showModal });
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,106 @@
|
||||
import { h } from 'vue';
|
||||
import { NAvatar, NAvatarGroup, NTooltip } from 'naive-ui';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '消息标题',
|
||||
key: 'title',
|
||||
render(row) {
|
||||
return h('p', { id: 'app' }, [
|
||||
h('div', {
|
||||
innerHTML: '<div style="white-space: pre-wrap">' + row.title + '</div>',
|
||||
}),
|
||||
]);
|
||||
},
|
||||
width: 280,
|
||||
},
|
||||
{
|
||||
title: '消息类型',
|
||||
key: 'type',
|
||||
render(row) {
|
||||
return renderOptionTag('noticeTypeOptions', row.type);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
key: 'tag',
|
||||
render(row) {
|
||||
return renderOptionTag('noticeTagOptions', row.tag);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '接收人',
|
||||
key: 'receiver',
|
||||
render(row) {
|
||||
if (row.type === 1 || row.type === 2) {
|
||||
return '所有人';
|
||||
}
|
||||
return h(
|
||||
NAvatarGroup,
|
||||
{
|
||||
max: 4,
|
||||
size: 40,
|
||||
options: row.receiverGroup,
|
||||
},
|
||||
{
|
||||
avatar: (column) =>
|
||||
h(NTooltip, null, {
|
||||
trigger: () =>
|
||||
column.option.src !== ''
|
||||
? h(NAvatar, {
|
||||
src: column.option.src,
|
||||
round: true,
|
||||
size: 32,
|
||||
style: {
|
||||
marginRight: '4px',
|
||||
},
|
||||
})
|
||||
: h(
|
||||
NAvatar,
|
||||
{
|
||||
round: true,
|
||||
size: 32,
|
||||
style: {
|
||||
marginRight: '4px',
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => column.option.name?.substring(0, 1) as string,
|
||||
}
|
||||
),
|
||||
default: () => column.option.name,
|
||||
}),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '阅读量',
|
||||
key: 'readCount',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '发送时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-drawer v-model:show="showModal" :width="dialogWidth">
|
||||
<n-drawer-content closable>
|
||||
<template #header>
|
||||
{{
|
||||
formValue.id > 0
|
||||
? '编辑' + dict.getLabel('noticeTypeOptions', formValue.type) + ' #' + formValue.id
|
||||
: '发送' + dict.getLabel('noticeTypeOptions', formValue.type)
|
||||
}}
|
||||
</template>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-alert :show-icon="false" type="info">
|
||||
消息发送成功后如果接收人在线会立即收到一条消息通知,编辑已发送的消息不会再次通知
|
||||
</n-alert>
|
||||
<n-form
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
:label-placement="settingStore.isMobile ? 'top' : 'left'"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="消息标题" path="title">
|
||||
<n-input placeholder="请输入消息标题" v-model:value="formValue.title" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="接收人" path="receiver" v-if="formValue.type === 3">
|
||||
<n-select
|
||||
multiple
|
||||
:options="options"
|
||||
:render-label="renderLabel"
|
||||
:render-tag="renderMultipleSelectTag"
|
||||
v-model:value="formValue.receiver"
|
||||
filterable
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="消息内容" path="content">
|
||||
<template v-if="formValue.type === 1">
|
||||
<n-input
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 10 }"
|
||||
placeholder="请输入通知内容"
|
||||
v-model:value="formValue.content"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<Editor style="height: 320px" v-model:value="formValue.content" />
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="标签" path="tag">
|
||||
<n-select
|
||||
clearable
|
||||
placeholder="可以不填"
|
||||
:render-tag="renderTag"
|
||||
v-model:value="formValue.tag"
|
||||
:options="dict.getOptionUnRef('noticeTagOptions')"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number style="width: 100%" v-model:value="formValue.sort" clearable />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="formValue.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="备注" path="remark">
|
||||
<n-input
|
||||
type="textarea"
|
||||
placeholder="请输入备注,没有可以不填"
|
||||
v-model:value="formValue.remark"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</n-scrollbar>
|
||||
<template #footer>
|
||||
<n-space>
|
||||
<n-button @click="closeForm"> 取消 </n-button>
|
||||
<n-button type="primary" :loading="formBtnLoading" @click="confirmForm">
|
||||
立即发送
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { personOption, renderLabel, renderMultipleSelectTag } from '@/enums/systemMessageEnum';
|
||||
import Editor from '@/components/Editor/editor.vue';
|
||||
import { statusOptions } from '@/enums/optionsiEnum';
|
||||
import { GetMemberOption } from '@/api/org/user';
|
||||
import { MaxSort, EditLetter, EditNotice, EditNotify } from '@/api/apply/notice';
|
||||
import { renderTag } from '@/utils';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { State, newState, rules } from './model';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const dict = useDictStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const options = ref<personOption[]>();
|
||||
const dialogWidth = ref(adaModalWidth(840));
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
switch (formValue.value.type) {
|
||||
case 1:
|
||||
EditNotify(formValue.value).then((_res) => {
|
||||
confirmComplete();
|
||||
});
|
||||
break;
|
||||
case 2:
|
||||
EditNotice(formValue.value).then((_res) => {
|
||||
confirmComplete();
|
||||
});
|
||||
break;
|
||||
case 3:
|
||||
EditLetter(formValue.value).then((_res) => {
|
||||
confirmComplete();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
message.error('公告类型不支持');
|
||||
}
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function confirmComplete() {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
closeForm();
|
||||
emit('reloadTable');
|
||||
});
|
||||
}
|
||||
|
||||
function getMemberOption() {
|
||||
GetMemberOption().then((res) => {
|
||||
options.value = res;
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭抽屉
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 打开抽屉
|
||||
function openModal(state: State, type: number) {
|
||||
showModal.value = true;
|
||||
dialogWidth.value = adaModalWidth(840);
|
||||
getMemberOption();
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
formValue.value.type = type;
|
||||
|
||||
loading.value = true;
|
||||
MaxSort()
|
||||
.then((res) => {
|
||||
formValue.value.sort = res.sort;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
formValue.value = newState(state);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="通知公告">
|
||||
在这里,您可以向平台中的用户发送通知、公告和私信消息
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
@keyup.enter="handleSubmit"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="warning"
|
||||
@click="addTable(1)"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/notice/editNotify'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<NotificationOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
发通知
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
type="error"
|
||||
@click="addTable(2)"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/notice/editNotice'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<BellOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
发公告
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
type="info"
|
||||
@click="addTable(3)"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/notice/editLetter'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<SendOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
发私信
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
type="error"
|
||||
@click="batchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/notice/delete'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit ref="editRef" @reload-table="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { Delete, List, Status } from '@/api/apply/notice';
|
||||
import { BellOutlined, DeleteOutlined, NotificationOutlined, SendOutlined } from '@vicons/antd';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { columns } from './columns';
|
||||
import { schemas, loadOptions } from './model';
|
||||
import Edit from './edit.vue';
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const message = useMessage();
|
||||
const dict = useDictStore();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const searchFormRef = ref<any>({});
|
||||
const editRef = ref();
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 200,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '已启用',
|
||||
onClick: handleStatus.bind(null, record, 2),
|
||||
ifShow: () => {
|
||||
return record.status === 1;
|
||||
},
|
||||
auth: ['/notice/status'],
|
||||
},
|
||||
{
|
||||
label: '已禁用',
|
||||
onClick: handleStatus.bind(null, record, 1),
|
||||
ifShow: () => {
|
||||
return record.status === 2;
|
||||
},
|
||||
auth: ['/notice/status'],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['/notice/edit'],
|
||||
type: 'primary',
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/notice/delete'],
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function addTable(type) {
|
||||
editRef.value.openModal(null, type);
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
editRef.value.openModal(record, record.type);
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function batchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleStatus(record: Recordable, status: number) {
|
||||
Status({ id: record.id, status: status }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,113 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
export class State {
|
||||
id: number;
|
||||
title: string;
|
||||
type: number;
|
||||
tag: number = 1;
|
||||
content: string;
|
||||
receiver: number[];
|
||||
remark: string;
|
||||
sort: number;
|
||||
status: number = 1;
|
||||
createdBy: number;
|
||||
updatedBy: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
readCount: number;
|
||||
receiverGroup: Receiver[];
|
||||
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Receiver {
|
||||
name: string;
|
||||
src: string;
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
if (state instanceof State) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return new State(state);
|
||||
}
|
||||
return new State();
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const rules = {
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入消息标题',
|
||||
},
|
||||
};
|
||||
|
||||
// 表格搜索表单
|
||||
export const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'type',
|
||||
component: 'NSelect',
|
||||
label: '消息类型',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择消息类型',
|
||||
options: dict.getOption('noticeTypeOptions'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '消息标题',
|
||||
componentProps: {
|
||||
placeholder: '请输入消息标题',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入消息标题', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
component: 'NInput',
|
||||
label: '消息内容',
|
||||
componentProps: {
|
||||
placeholder: '请输入消息内容关键词',
|
||||
showButton: false,
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择类型',
|
||||
options: dict.getOption('sys_normal_disable'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['sys_normal_disable']);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:title="isUpdate ? '编辑 #' + params?.id : '添加'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-form
|
||||
:model="params"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="上级地区" path="pid">
|
||||
<n-tree-select :options="optionTreeData" :default-value="params.pid" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="地区ID" path="id">
|
||||
<n-input-number
|
||||
style="width: 100%"
|
||||
placeholder="请输入地区ID"
|
||||
v-model:value="params.id"
|
||||
:disabled="isUpdate"
|
||||
path="handleChangeId"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="地区名称" path="title">
|
||||
<n-input placeholder="请输入地区名称" v-model:value="params.title" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="拼音" path="pinyin">
|
||||
<n-input placeholder="请输入拼音" v-model:value="params.pinyin" />
|
||||
</n-form-item>
|
||||
|
||||
<n-grid x-gap="24" :cols="2">
|
||||
<n-gi>
|
||||
<n-form-item label="经度" path="lng">
|
||||
<n-input placeholder="经度" v-model:value="params.lng" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="纬度" path="lat">
|
||||
<n-input placeholder="纬度" v-model:value="params.lat" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number v-model:value="params.sort" clearable />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="状态" path="status">
|
||||
<n-radio-group v-model:value="params.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in dict.getOptionUnRef('sys_normal_disable')"
|
||||
:key="Number(status.value)"
|
||||
:value="Number(status.value)"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { State, newState } from './model';
|
||||
import { Edit, MaxSort, CheckProvincesUniqueId } from '@/api/apply/provinces';
|
||||
import { FormItemRule, useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
formParams?: State;
|
||||
optionTreeData: any;
|
||||
isUpdate: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
formParams: () => {
|
||||
return newState(null);
|
||||
},
|
||||
optionTreeData: [],
|
||||
isUpdate: false,
|
||||
});
|
||||
|
||||
const isShowModal = computed({
|
||||
get: () => {
|
||||
return props.showModal;
|
||||
},
|
||||
set: (value) => {
|
||||
emit('updateShowModal', value);
|
||||
},
|
||||
});
|
||||
|
||||
const params = computed(() => {
|
||||
return props.formParams;
|
||||
});
|
||||
|
||||
const rules = {
|
||||
id: {
|
||||
required: true,
|
||||
async validator(rule: FormItemRule, value: string, callback: Function) {
|
||||
if (!value) {
|
||||
callback(new Error('请填写地区ID'));
|
||||
} else if (!/^\d*$/.test(value)) {
|
||||
callback(new Error('地区ID应该为整数'));
|
||||
} else if (!(await isUniqueId(value))) {
|
||||
callback(new Error('地区ID已存在'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
message: '请输入地区名称',
|
||||
},
|
||||
};
|
||||
|
||||
const dict = useDictStore();
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Edit(params.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
isShowModal.value = false;
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => params.value,
|
||||
(value) => {
|
||||
params.value.oldId = Number(value.id);
|
||||
if (value.id === 0 || value.id === null) {
|
||||
MaxSort().then((res) => {
|
||||
params.value.sort = res.sort;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
async function isUniqueId(newId: any) {
|
||||
const res = await CheckProvincesUniqueId({ oldId: params.value.oldId, newId: newId });
|
||||
return res.unique;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="省市区"> 中国省市区编码对照表 </n-card>
|
||||
</div>
|
||||
<n-grid cols="1 s:1 m:1 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12">
|
||||
<n-gi span="1">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small" class="proCard">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-button type="info" icon-placement="left" @click="openCreateDrawer">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button
|
||||
type="info"
|
||||
icon-placement="left"
|
||||
@click="openEditDrawer"
|
||||
:disabled="formParams?.id === null || formParams?.id <= 0"
|
||||
>
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<EditOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
编辑
|
||||
</n-button>
|
||||
<n-button type="error" icon-placement="left" @click="handleDel">
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
删除
|
||||
</n-button>
|
||||
<n-button type="info" icon-placement="left" @click="packHandle">
|
||||
{{ expandedKeys.length ? '收起' : '展开' }}
|
||||
<template #icon>
|
||||
<div class="flex items-center">
|
||||
<n-icon size="14">
|
||||
<AlignLeftOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</template>
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
<div class="w-full menu">
|
||||
<n-input v-model:value="pattern" placeholder="输入地区名称搜索">
|
||||
<template #suffix>
|
||||
<n-icon size="18" class="cursor-pointer">
|
||||
<SearchOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-input>
|
||||
<div class="py-3 menu-list">
|
||||
<template v-if="loading">
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<n-spin size="medium" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-tree
|
||||
block-line
|
||||
cascade
|
||||
checkable
|
||||
:virtual-scroll="true"
|
||||
:pattern="pattern"
|
||||
:data="treeData"
|
||||
:expandedKeys="expandedKeys"
|
||||
style="height: 75vh"
|
||||
@update:selected-keys="selectedTree"
|
||||
@update:expanded-keys="onExpandedKeys"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi span="3">
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small" class="proCard">
|
||||
<template #header>
|
||||
<n-space>
|
||||
<n-icon size="18">
|
||||
<FormOutlined />
|
||||
</n-icon>
|
||||
<span>编辑省市区{{ treeItemTitle ? `:${treeItemTitle}` : '' }}</span>
|
||||
<span style="font-size: 14px">{{
|
||||
treeItemTitle ? '' : '从列表选择一项后,进行编辑'
|
||||
}}</span>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<List :checkedId="checkedId" :optionTreeData="optionTreeData" @reload-table="loadData" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<Edit
|
||||
@reload-table="loadData"
|
||||
@update-show-modal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
:optionTreeData="optionTreeData"
|
||||
:isUpdate="isUpdate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, unref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import {
|
||||
AlignLeftOutlined,
|
||||
FormOutlined,
|
||||
PlusOutlined,
|
||||
EditOutlined,
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
} from '@vicons/antd';
|
||||
import List from './list.vue';
|
||||
import { getProvincesTree, Delete } from '@/api/apply/provinces';
|
||||
import Edit from './edit.vue';
|
||||
import { newState, loadOptions } from './model';
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const showModal = ref(false);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
let treeItemKey = ref([]);
|
||||
let expandedKeys = ref([]);
|
||||
const treeData = ref([]);
|
||||
const loading = ref(true);
|
||||
const treeItemTitle = ref('');
|
||||
const checkedId = ref(0);
|
||||
const pattern = ref('');
|
||||
const optionTreeData = ref<any>([]);
|
||||
const formParams = ref(newState(null));
|
||||
|
||||
function openCreateDrawer() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
isUpdate.value = false;
|
||||
}
|
||||
|
||||
function openEditDrawer() {
|
||||
showModal.value = true;
|
||||
isUpdate.value = true;
|
||||
}
|
||||
|
||||
function selectedTree(keys, opts) {
|
||||
if (keys.length) {
|
||||
const treeItem = opts[0];
|
||||
treeItemKey.value = keys;
|
||||
treeItemTitle.value = treeItem.label;
|
||||
formParams.value = newState(treeItem);
|
||||
checkedId.value = treeItem.id;
|
||||
} else {
|
||||
treeItemKey.value = [];
|
||||
treeItemTitle.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function handleDel() {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: `您确定想删除吗?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ ...formParams.value }).then(async (_res) => {
|
||||
message.success('操作成功');
|
||||
await loadData();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
message.error('已取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function packHandle() {
|
||||
if (expandedKeys.value.length) {
|
||||
expandedKeys.value = [];
|
||||
} else {
|
||||
expandedKeys.value = unref(treeData).map((item: any) => item.key as string) as [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const treeMenuList = await getProvincesTree();
|
||||
Object.assign(
|
||||
formParams,
|
||||
treeMenuList.list.map((item) => item.key)
|
||||
);
|
||||
treeData.value = [];
|
||||
optionTreeData.value = [
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '顶级地区',
|
||||
pid: 0,
|
||||
title: '顶级地区',
|
||||
},
|
||||
];
|
||||
treeData.value = treeMenuList.list;
|
||||
optionTreeData.value = optionTreeData.value.concat(treeMenuList.list);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function onExpandedKeys(keys) {
|
||||
expandedKeys.value = keys;
|
||||
}
|
||||
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadOptions();
|
||||
await loadData();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-result
|
||||
v-show="checkedId <= 0"
|
||||
status="info"
|
||||
title="提示"
|
||||
description="请选择一个想要编辑的省市区"
|
||||
/>
|
||||
|
||||
<div v-show="checkedId > 0">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:columns="listColumns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加数据
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</div>
|
||||
|
||||
<Edit
|
||||
@reloadTable="reloadTable"
|
||||
@updateShowModal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
:optionTreeData="optionTreeData"
|
||||
:isUpdate="isUpdate"
|
||||
/>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, watch, computed } from 'vue';
|
||||
import { useMessage, useDialog } from 'naive-ui';
|
||||
import { BasicColumn, BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { listColumns, newState, State } from './model';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { getProvincesChildrenList, Delete } from '@/api/apply/provinces';
|
||||
import Edit from './edit.vue';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
|
||||
interface Props {
|
||||
checkedId?: number;
|
||||
optionTreeData: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), { checkedId: 0, optionTreeData: [] });
|
||||
const searchFormRef = ref<any>({});
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const actionRef = ref();
|
||||
const isUpdate = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formParams = ref<State>(newState(null));
|
||||
const params = ref({
|
||||
pageSize: 10,
|
||||
pid: props.checkedId,
|
||||
label: '',
|
||||
});
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'id',
|
||||
component: 'NInput',
|
||||
label: '地区ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入地区ID',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
params.value.label = e;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '地区名称',
|
||||
componentProps: {
|
||||
placeholder: '请输入地区名称',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
params.value.label = e;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const actionColumn = reactive<BasicColumn>({
|
||||
width: 150,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(listColumns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:1 l:2 xl:2 2xl:2' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
formParams.value.pid = props.checkedId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
if (props.checkedId <= 0) {
|
||||
return [];
|
||||
}
|
||||
return await getProvincesChildrenList({
|
||||
...{ pid: props.checkedId },
|
||||
...searchFormRef.value?.formModel,
|
||||
...res,
|
||||
});
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
emit('reloadTable');
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '您确定想删除吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(record as State);
|
||||
isUpdate.value = true;
|
||||
}
|
||||
|
||||
function handleSubmit(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {
|
||||
params.value.label = '';
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
watch(props, (_newVal, _oldVal) => {
|
||||
if (params.value.pid === _newVal.checkedId) {
|
||||
return;
|
||||
}
|
||||
params.value.pid = _newVal.checkedId;
|
||||
formParams.value.pid = Number(_newVal.checkedId);
|
||||
if (_newVal.checkedId > 0) {
|
||||
reloadTable();
|
||||
}
|
||||
});
|
||||
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,87 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
export const listColumns = [
|
||||
{
|
||||
title: '地区ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '地区名称',
|
||||
key: 'title',
|
||||
},
|
||||
{
|
||||
title: '拼音',
|
||||
key: 'pinyin',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'success',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.pinyin,
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '经度',
|
||||
key: 'lng',
|
||||
},
|
||||
{
|
||||
title: '维度',
|
||||
key: 'lat',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return renderOptionTag('sys_normal_disable', row.status);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export interface State {
|
||||
id: number | null;
|
||||
title: string;
|
||||
pinyin: string;
|
||||
lng: string;
|
||||
lat: string;
|
||||
pid: number;
|
||||
sort: number;
|
||||
status: number;
|
||||
oldId: number;
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
id: null,
|
||||
title: '',
|
||||
pinyin: '',
|
||||
lng: '',
|
||||
lat: '',
|
||||
pid: 0,
|
||||
sort: 0,
|
||||
status: 1,
|
||||
oldId: 0,
|
||||
};
|
||||
|
||||
export function newState(state: State | null): State {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['sys_normal_disable']);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { h } from 'vue';
|
||||
import { NTag } from 'naive-ui';
|
||||
import { renderPopoverMemberSumma } from '@/utils';
|
||||
|
||||
const msgMap = {
|
||||
1: '处理中',
|
||||
2: '提现成功',
|
||||
3: ' 提现异常',
|
||||
};
|
||||
|
||||
export const statusOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '处理中',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '提现成功',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '提现异常',
|
||||
},
|
||||
];
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '提现ID',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '申请人',
|
||||
key: 'memberId',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return renderPopoverMemberSumma(row.memberBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '提现金额',
|
||||
key: 'money',
|
||||
render(row) {
|
||||
return row.money.toFixed(2);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '手续费',
|
||||
key: 'fee',
|
||||
render(row) {
|
||||
return row.fee.toFixed(2);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '最终到账',
|
||||
key: 'lastMoney',
|
||||
render(row) {
|
||||
return row.lastMoney.toFixed(2);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '处理结果',
|
||||
key: 'msg',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: row.status == 1 ? 'info' : row.status == 2 ? 'success' : 'warning',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => (row.msg == '' ? msgMap[row.status] : row.msg),
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '申请IP',
|
||||
key: 'ip',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '处理时间',
|
||||
key: 'handleAt',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard" title="提现管理">
|
||||
<n-tabs
|
||||
type="card"
|
||||
class="card-tabs"
|
||||
:value="defaultTab"
|
||||
animated
|
||||
@before-leave="handleBeforeLeave"
|
||||
>
|
||||
<n-tab-pane name="" tab="全部"> <List :type="defaultTab" /></n-tab-pane>
|
||||
<n-tab-pane name="1" tab="处理中"> <List :type="defaultTab" /> </n-tab-pane>
|
||||
<n-tab-pane name="2" tab="提现成功"> <List :type="defaultTab" /> </n-tab-pane>
|
||||
<n-tab-pane name="3" tab="提现异常"> <List :type="defaultTab" /> </n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import List from './list.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const defaultTab = ref('');
|
||||
|
||||
onMounted(() => {
|
||||
if (router.currentRoute.value.query?.type) {
|
||||
defaultTab.value = router.currentRoute.value.query.type as string;
|
||||
}
|
||||
});
|
||||
|
||||
function handleBeforeLeave(tabName: string) {
|
||||
defaultTab.value = tabName;
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,408 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
@keyup.enter="handleSubmit"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<MoneyCollectOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
申请提现
|
||||
</n-button>
|
||||
|
||||
<n-button type="default" @click="setCash" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<EditOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
设置提现账户
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="申请提现"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-alert type="info">
|
||||
<div v-html="config.cashTips"></div>
|
||||
</n-alert>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="可提现金额">
|
||||
<n-input v-model:value="newUserInfo.balance" disabled />
|
||||
<template #feedback
|
||||
><p>{{ estimated }}</p></template
|
||||
>
|
||||
</n-form-item>
|
||||
<br />
|
||||
<n-form-item label="提现金额" path="money">
|
||||
<n-input-number v-model:value="formParams.money" :min="1" :max="newUserInfo.balance">
|
||||
<template #minus-icon>
|
||||
<n-icon :component="ArrowDownCircleOutline" />
|
||||
</template>
|
||||
<template #add-icon>
|
||||
<n-icon :component="ArrowUpCircleOutline" />
|
||||
</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showPaymentModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="处理打款"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-form
|
||||
:model="paymentParams"
|
||||
ref="PaymentRef"
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="最终到账金额">
|
||||
<n-input v-model:value="paymentParams.lastMoney" disabled />
|
||||
</n-form-item>
|
||||
<n-form-item label="收款信息">
|
||||
<n-input v-model:value="paymentParams.accountInfo" disabled />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="收款码">
|
||||
<n-carousel draggable>
|
||||
<img style="width: 200px" class="carousel-img" :src="paymentParams.payeeCode" />
|
||||
</n-carousel>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="变更提现状态" path="status">
|
||||
<n-radio-group v-model:value="paymentParams.status" name="status">
|
||||
<n-radio-button
|
||||
v-for="status in statusOptions"
|
||||
:key="status.value"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
/>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="处理结果">
|
||||
<n-input v-model:value="paymentParams.msg" />
|
||||
<template #feedback>不填默认显示提现状态</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showPaymentModal = false)">取消</n-button>
|
||||
<n-button type="info" :loading="PaymentBtnLoading" @click="confirmPayment"
|
||||
>确定</n-button
|
||||
>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { Apply, List, Payment, View } from '@/api/cash';
|
||||
import { columns, statusOptions } from './columns';
|
||||
import { ArrowDownCircleOutline, ArrowUpCircleOutline } from '@vicons/ionicons5';
|
||||
import { MoneyCollectOutlined, EditOutlined } from '@vicons/antd';
|
||||
import { defRangeShortcuts, timestampToTime } from '@/utils/dateUtil';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { getUserInfo } from '@/api/system/user';
|
||||
import { getCashConfig } from '@/api/sys/config';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
interface Props {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: '',
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
const params = ref<any>({
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
content: '',
|
||||
status: null,
|
||||
});
|
||||
|
||||
const estimated = ref(
|
||||
'本次提现预计将在 ' +
|
||||
timestampToTime(new Date().setTime(new Date().getTime() + 86400 * 4 * 1000) / 1000) +
|
||||
' 前到账 (1-3个工作日,双休日和法定节假日顺延)'
|
||||
);
|
||||
|
||||
const schemas: FormSchema[] = [
|
||||
{
|
||||
field: 'complexMemberId',
|
||||
component: 'ComplexMemberPicker',
|
||||
label: '申请人',
|
||||
componentProps: {
|
||||
placeholder: '请选择申请人',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '申请IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入申请IP',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ message: '请输入申请IP', trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'created_at',
|
||||
component: 'NDatePicker',
|
||||
label: '申请时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const newUserInfo = ref({ balance: 0 });
|
||||
const message = useMessage();
|
||||
const actionRef = ref();
|
||||
const showModal = ref(false);
|
||||
const showPaymentModal = ref(false);
|
||||
const PaymentRef = ref<any>({});
|
||||
const PaymentBtnLoading = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const searchFormRef = ref<any>({});
|
||||
const formRef = ref<any>({});
|
||||
const config = ref<any>({
|
||||
cashMinFee: 3,
|
||||
cashMinFeeRatio: '0.03',
|
||||
cashMinMoney: 0,
|
||||
cashSwitch: false,
|
||||
cashTips: '',
|
||||
});
|
||||
|
||||
const resetFormParams = {
|
||||
money: null,
|
||||
accountInfo: null,
|
||||
};
|
||||
let formParams = ref<any>(resetFormParams);
|
||||
|
||||
const resetPaymentParams = {
|
||||
id: null,
|
||||
money: null,
|
||||
};
|
||||
let paymentParams = ref<any>(resetPaymentParams);
|
||||
|
||||
const actionColumn = reactive({
|
||||
auth: ['/cash/payment'],
|
||||
width: 100,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '处理打款',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
function setCash() {
|
||||
router.push({
|
||||
name: 'home_account',
|
||||
query: {
|
||||
type: 3,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
async function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = resetFormParams;
|
||||
newUserInfo.value = await getUserInfo();
|
||||
|
||||
if (newUserInfo.value.balance < config.value.cashMinMoney) {
|
||||
message.error('当前余额不满足提现条件,至少需要:' + config.value.cashMinMoney + '元');
|
||||
}
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
mapWidth();
|
||||
config.value = await getCashConfig();
|
||||
config.value = config.value.list;
|
||||
return await List({
|
||||
...params.value,
|
||||
...res,
|
||||
...searchFormRef.value.formModel,
|
||||
...{ status: props.type },
|
||||
});
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请提现
|
||||
* @param e
|
||||
*/
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Apply({ money: formParams.value.money }).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showModal.value = false;
|
||||
reloadTable();
|
||||
formParams.value = ref(resetFormParams);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理打款
|
||||
* @param e
|
||||
*/
|
||||
function confirmPayment(e) {
|
||||
e.preventDefault();
|
||||
PaymentBtnLoading.value = true;
|
||||
PaymentRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
Payment({
|
||||
id: PaymentRef.value.model.id,
|
||||
status: PaymentRef.value.model.status,
|
||||
msg: PaymentRef.value.model.msg,
|
||||
}).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
showPaymentModal.value = false;
|
||||
reloadTable();
|
||||
PaymentRef.value = ref(resetPaymentParams);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
PaymentBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
async function handleEdit(record: Recordable) {
|
||||
showPaymentModal.value = true;
|
||||
paymentParams.value = record;
|
||||
paymentParams.value = await View({ id: record.id });
|
||||
paymentParams.value.lastMoney = paymentParams.value.lastMoney.toFixed(2);
|
||||
paymentParams.value.accountInfo =
|
||||
paymentParams.value.name + ' - ' + paymentParams.value.account;
|
||||
}
|
||||
|
||||
function handleSubmit(values: Recordable) {
|
||||
console.log(values);
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(values: Recordable) {
|
||||
params.value = values;
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
const dialogWidth = ref('50%');
|
||||
|
||||
function mapWidth() {
|
||||
let val = document.body.clientWidth;
|
||||
const def = 720; // 默认宽度
|
||||
if (val < def) {
|
||||
dialogWidth.value = '100%';
|
||||
} else {
|
||||
dialogWidth.value = def + 'px';
|
||||
}
|
||||
|
||||
return dialogWidth.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="资金变动">
|
||||
你和下级在平台中余额、积分的变动明细都可以在这里进行查看
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-tabs
|
||||
type="card"
|
||||
class="card-tabs"
|
||||
:value="defaultTab"
|
||||
animated
|
||||
@before-leave="handleBeforeLeave"
|
||||
>
|
||||
<n-tab-pane name="" tab="全部"> <List :type="defaultTab" /></n-tab-pane>
|
||||
<n-tab-pane name="balance" tab="余额明细"> <List :type="defaultTab" /> </n-tab-pane>
|
||||
<n-tab-pane name="integral" tab="积分明细"> <List :type="defaultTab" /> </n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import List from './list.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { loadOptions } from './model';
|
||||
|
||||
const router = useRouter();
|
||||
const defaultTab = ref('');
|
||||
|
||||
function handleBeforeLeave(tabName: string) {
|
||||
defaultTab.value = tabName;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (router.currentRoute.value.query?.type) {
|
||||
defaultTab.value = router.currentRoute.value.query.type as string;
|
||||
}
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="false"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="handleExport"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/creditsLog/export'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { List, Export } from '@/api/creditsLog';
|
||||
import { columns, schemas } from './model';
|
||||
import { ExportOutlined } from '@vicons/antd';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
interface Props {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: '',
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, 0);
|
||||
});
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>({});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({
|
||||
...searchFormRef.value?.formModel,
|
||||
...res,
|
||||
...{ creditType: props.type },
|
||||
});
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,166 @@
|
||||
import { ref } from 'vue';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag, renderPopoverMemberSumma } from '@/utils';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'complexMemberId',
|
||||
component: 'ComplexMemberPicker',
|
||||
label: '选择用户',
|
||||
componentProps: {
|
||||
placeholder: '请选择用户',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'creditGroup',
|
||||
component: 'NSelect',
|
||||
label: '变动组别',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择变动组别',
|
||||
options: dict.getOption('creditGroup'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
component: 'NInput',
|
||||
label: '备注',
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '操作人IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入操作人IP',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
component: 'NInput',
|
||||
label: '变动ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入变动ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '变动时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '变动ID',
|
||||
key: 'id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '用户',
|
||||
key: 'memberId',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return renderPopoverMemberSumma(row.memberBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '变动类型',
|
||||
key: 'creditType',
|
||||
render(row) {
|
||||
return renderOptionTag('creditType', row.creditType);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '变动组别',
|
||||
key: 'creditGroup',
|
||||
render(row) {
|
||||
return renderOptionTag('creditGroup', row.creditGroup);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '变动前',
|
||||
key: 'beforeNum',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return Number(row.beforeNum).toFixed(2);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '变动数量',
|
||||
key: 'num',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return Number(row.num).toFixed(2);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '变动后',
|
||||
key: 'afterNum',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return Number(row.afterNum).toFixed(2);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '操作人IP',
|
||||
key: 'ip',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '关联ID',
|
||||
key: 'mapId',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (row.mapId === 0) {
|
||||
return '-';
|
||||
}
|
||||
return row.mapId;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '变动时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['creditType', 'creditGroup']);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="交易退款">
|
||||
<!-- 这是系统自动生成的CURD表格,你可以将此行注释改为表格的描述 -->
|
||||
</n-card>
|
||||
</div>
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="false"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:scroll-x="1090"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="handleExport"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/creditsLog/export'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { List, Export } from '@/api/pay/refund';
|
||||
import { columns, schemas, loadOptions } from './model';
|
||||
import { ExportOutlined } from '@vicons/antd';
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>({});
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,181 @@
|
||||
import { ref } from 'vue';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { renderOptionTag } from '@/utils';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'id',
|
||||
component: 'NInputNumber',
|
||||
label: '变动ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入变动ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'memberId',
|
||||
component: 'NInputNumber',
|
||||
label: '管理员ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入管理员ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'appId',
|
||||
component: 'NInput',
|
||||
label: '应用id',
|
||||
componentProps: {
|
||||
placeholder: '请输入应用id',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'creditType',
|
||||
component: 'NSelect',
|
||||
label: '变动类型',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择变动类型',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'creditGroup',
|
||||
component: 'NSelect',
|
||||
label: '变动的组别',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择变动的组别',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
component: 'NInput',
|
||||
label: '备注',
|
||||
componentProps: {
|
||||
placeholder: '请输入备注',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'ip',
|
||||
component: 'NInput',
|
||||
label: '操作人IP',
|
||||
componentProps: {
|
||||
placeholder: '请输入操作人IP',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '状态',
|
||||
defaultValue: null,
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '变动ID',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '管理员ID',
|
||||
key: 'memberId',
|
||||
},
|
||||
{
|
||||
title: '应用id',
|
||||
key: 'appId',
|
||||
},
|
||||
{
|
||||
title: '插件名称',
|
||||
key: 'addonsName',
|
||||
},
|
||||
{
|
||||
title: '变动前',
|
||||
key: 'beforeNum',
|
||||
},
|
||||
{
|
||||
title: '变动数据',
|
||||
key: 'num',
|
||||
},
|
||||
{
|
||||
title: '变动后',
|
||||
key: 'afterNum',
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
key: 'remark',
|
||||
},
|
||||
{
|
||||
title: '操作人IP',
|
||||
key: 'ip',
|
||||
},
|
||||
{
|
||||
title: '关联ID',
|
||||
key: 'mapId',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return renderOptionTag('sys_normal_disable', row.status);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
key: 'updatedAt',
|
||||
},
|
||||
];
|
||||
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['sys_normal_disable']);
|
||||
}
|
||||
|
||||
loadOptions();
|
||||
@@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="在线充值"> 余额可用于购买付费产品或商城消费 </n-card>
|
||||
</div>
|
||||
<n-spin :show="loading">
|
||||
<n-grid cols="1 s:1 m:1 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12">
|
||||
<n-gi span="4">
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-thing>
|
||||
<template #description> <span class="title">充值金额</span> </template>
|
||||
<n-space>
|
||||
<n-button
|
||||
type="primary"
|
||||
ghost
|
||||
v-for="item in amounts"
|
||||
:key="item"
|
||||
@click="SetAmount(item)"
|
||||
>
|
||||
¥{{ item }}
|
||||
|
||||
<n-icon
|
||||
class="check-icon"
|
||||
:size="18"
|
||||
:component="CheckOutlined"
|
||||
v-if="amount === item && amountType === 1"
|
||||
/>
|
||||
</n-button>
|
||||
|
||||
<n-input-number v-model:value="amount" v-if="amountType === 2">
|
||||
<template #prefix> ¥ </template>
|
||||
</n-input-number>
|
||||
</n-space>
|
||||
<template #footer> <span class="title">支付方式 </span></template>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button
|
||||
strong
|
||||
secondary
|
||||
:color="item.color"
|
||||
v-for="item in payTypes"
|
||||
:key="item"
|
||||
@click="SetPayType(item.value)"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="item.icon" />
|
||||
</template>
|
||||
{{ item.label }}
|
||||
<n-icon
|
||||
class="check-icon"
|
||||
:size="18"
|
||||
:component="CheckOutlined"
|
||||
v-if="payType === item.value"
|
||||
/>
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
<n-button
|
||||
type="success"
|
||||
class="create-order-button"
|
||||
size="large"
|
||||
@click="CreateOrder"
|
||||
>
|
||||
立即充值
|
||||
</n-button>
|
||||
</template>
|
||||
</n-thing>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-spin>
|
||||
|
||||
<n-modal v-model:show="showQrModal" :show-icon="false" preset="dialog" :title="qrParams.name">
|
||||
<n-form class="py-4">
|
||||
<div class="text-center">
|
||||
<qrcode-vue :value="qrParams.qrUrl" :size="220" class="canvas" style="margin: 0 auto" />
|
||||
</div>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showQrModal = false)">关闭</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
<RechargeLog class="mt-6" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, inject } from 'vue';
|
||||
import wx from 'weixin-js-sdk';
|
||||
import { WechatOutlined, AlipayOutlined, QqOutlined, CheckOutlined } from '@vicons/antd';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { Create } from '@/api/order';
|
||||
import QrcodeVue from 'qrcode.vue';
|
||||
import RechargeLog from '../rechargeLog/index.vue';
|
||||
import { SocketEnum } from '@/enums/socketEnum';
|
||||
import { addOnMessage } from '@/utils/websocket';
|
||||
|
||||
const showQrModal = ref(false);
|
||||
const qrParams = ref({
|
||||
name: '',
|
||||
qrUrl: '',
|
||||
});
|
||||
const loading = ref(false);
|
||||
const message = useMessage();
|
||||
const amountType = ref(1);
|
||||
const amounts = ref([0.01, 10, 20, 30, 50, 100, '其他金额']);
|
||||
const payTypes = ref([
|
||||
{ value: 'wxpay', label: '微信支付', icon: WechatOutlined, color: '#18a058' },
|
||||
{ value: 'alipay', label: '支付宝', icon: AlipayOutlined, color: '#2d8cf0' },
|
||||
{ value: 'qqpay', label: 'QQ支付', icon: QqOutlined, color: '#2d8cf0' },
|
||||
]);
|
||||
|
||||
const amount = ref<any>(null);
|
||||
const payType = ref<any>(null);
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
function SetPayType(type: string) {
|
||||
payType.value = type;
|
||||
}
|
||||
|
||||
function SetAmount(a: number | string) {
|
||||
amount.value = a;
|
||||
if (a === '其他金额') {
|
||||
amountType.value = 2;
|
||||
amount.value = null;
|
||||
} else {
|
||||
amountType.value = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function CreateOrder() {
|
||||
if (amount.value === null || amount.value <= 0) {
|
||||
message.error('请选择充值金额');
|
||||
return;
|
||||
}
|
||||
|
||||
if (payType.value === null || payType.value === '') {
|
||||
message.error('请选择支付方式');
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
Create({
|
||||
orderType: 'balance',
|
||||
payType: payType.value,
|
||||
money: amount.value,
|
||||
returnUrl: window.location.href,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.order?.tradeType === undefined || res.order?.tradeType === '') {
|
||||
message.error('创建支付订单失败,没找到交易方式,请联系管理处理!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.order?.tradeType !== 'mp') {
|
||||
if (res.order?.payURL === undefined || res.order?.payURL === '') {
|
||||
message.error('创建支付订单失败,没找到支付地址,请联系管理处理!');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (res.order?.tradeType) {
|
||||
case 'scan':
|
||||
showQr(res.order?.payURL, '打开微信【扫一扫】完成支付');
|
||||
break;
|
||||
case 'mp':
|
||||
if (res.order.jsApi === undefined) {
|
||||
message.error('支付失败请选择其他支付方式:JSAPI支付参数无效');
|
||||
return;
|
||||
}
|
||||
const jsApi = res.order.jsApi;
|
||||
|
||||
// 配置微信JS SDK
|
||||
wx.config({
|
||||
// debug: true,
|
||||
appId: jsApi.config.app_id,
|
||||
timestamp: jsApi.config.timestamp,
|
||||
nonceStr: jsApi.config.nonce_str,
|
||||
signature: jsApi.config.signature,
|
||||
jsApiList: ['chooseWXPay'],
|
||||
});
|
||||
// 配置完成后返回一个resolve
|
||||
wx.ready(() => {
|
||||
wxJSPay({
|
||||
timestamp: jsApi.params.timeStamp,
|
||||
nonceStr: jsApi.params.nonceStr,
|
||||
package: jsApi.params.package,
|
||||
signType: jsApi.params.signType,
|
||||
paySign: jsApi.params.paySign,
|
||||
})
|
||||
.then((_res) => {
|
||||
// ...
|
||||
})
|
||||
.catch((err) => {
|
||||
message.success('支付失败:', err.message);
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
case 'qqweb':
|
||||
showQr(res.order?.payURL, '打开QQ【扫一扫】完成支付');
|
||||
break;
|
||||
default:
|
||||
window.open(res.order?.payURL, '_blank');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 发起微信公众号支付
|
||||
function wxJSPay(params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 调用微信支付
|
||||
wx.chooseWXPay({
|
||||
timestamp: params.timestamp,
|
||||
nonceStr: params.nonceStr,
|
||||
package: params.package,
|
||||
signType: params.signType,
|
||||
paySign: params.paySign,
|
||||
success: (res) => {
|
||||
// 支付成功时返回resolve
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
// 支付失败时返回reject
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showQr(url: string, name: string) {
|
||||
qrParams.value.qrUrl = url;
|
||||
qrParams.value.name = name;
|
||||
showQrModal.value = true;
|
||||
}
|
||||
|
||||
const onMessageList = inject('onMessageList');
|
||||
|
||||
const handleMessageList = (res) => {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.event === SocketEnum.EventAdminOrderNotify) {
|
||||
if (data.code == SocketEnum.CodeErr) {
|
||||
message.error('查询出错:' + data.event);
|
||||
return;
|
||||
}
|
||||
|
||||
showQrModal.value = false;
|
||||
message.success('支付成功');
|
||||
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
addOnMessage(onMessageList, handleMessageList);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.n-thing .n-thing-main .n-thing-main__footer:not(:first-child)) {
|
||||
margin-top: 36px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
::v-deep(.title) {
|
||||
font-weight: var(--n-title-font-weight);
|
||||
transition: color 0.3s var(--n-bezier);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
color: var(--n-title-text-color);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
::v-deep(.check-icon) {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
::v-deep(.create-order-button) {
|
||||
margin-top: 28px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="受理退款申请"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-form
|
||||
:model="params"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="业务单号" path="orderSn">
|
||||
<n-input v-model:value="params.orderSn" :disabled="true" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="订单金额" path="money">
|
||||
<n-input placeholder="请输入标题" v-model:value="params.money" :disabled="true" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="退款原因" path="refundReason">
|
||||
<n-input
|
||||
type="textarea"
|
||||
placeholder="请填写退款原因"
|
||||
v-model:value="params.refundReason"
|
||||
:disabled="true"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="更新状态" path="status">
|
||||
<n-select
|
||||
v-model:value="params.status"
|
||||
:options="dict.getOptionUnRef('acceptRefundStatus')"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="拒绝原因" path="rejectRefundReason" v-if="params.status === 9">
|
||||
<n-input
|
||||
type="textarea"
|
||||
placeholder="请填拒绝退款原因"
|
||||
v-model:value="params.rejectRefundReason"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="退款备注" path="remark" v-if="params.status === 8">
|
||||
<n-input type="textarea" placeholder="请填退款备注" v-model:value="params.remark" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { rules, State, newState } from './model';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { AcceptRefund, View } from '@/api/order';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
formParams?: State;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
formParams: () => {
|
||||
return newState(null);
|
||||
},
|
||||
});
|
||||
|
||||
const isShowModal = computed({
|
||||
get: () => {
|
||||
return props.showModal;
|
||||
},
|
||||
set: (value) => {
|
||||
emit('updateShowModal', value);
|
||||
},
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const params = ref<State>(props.formParams);
|
||||
const dict = useDictStore();
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
AcceptRefund(params.value).then((_res) => {
|
||||
message.success('操作成功');
|
||||
setTimeout(() => {
|
||||
isShowModal.value = false;
|
||||
emit('reloadTable');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
function loadForm(value) {
|
||||
loading.value = true;
|
||||
|
||||
// 编辑
|
||||
View({ id: value.id })
|
||||
.then((res) => {
|
||||
params.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.formParams,
|
||||
(value) => {
|
||||
loadForm(value);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-modal
|
||||
v-model:show="isShowModal"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="退款申请"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-form
|
||||
:model="params"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="业务单号" path="orderSn">
|
||||
<n-input v-model:value="params.orderSn" :disabled="true" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="订单金额" path="money">
|
||||
<n-input placeholder="请输入标题" v-model:value="params.money" :disabled="true" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="退款原因" path="refundReason">
|
||||
<n-input
|
||||
type="textarea"
|
||||
placeholder="请填写退款原因"
|
||||
v-model:value="params.refundReason"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确定</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { rules, State, newState } from './model';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { ApplyRefund, View } from '@/api/order';
|
||||
|
||||
interface Props {
|
||||
showModal: boolean;
|
||||
formParams?: State;
|
||||
}
|
||||
|
||||
const emit = defineEmits(['reloadTable', 'updateShowModal']);
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
showModal: false,
|
||||
formParams: () => {
|
||||
return newState(null);
|
||||
},
|
||||
});
|
||||
|
||||
const isShowModal = computed({
|
||||
get: () => {
|
||||
return props.showModal;
|
||||
},
|
||||
set: (value) => {
|
||||
emit('updateShowModal', value);
|
||||
},
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const params = ref<State>(props.formParams);
|
||||
const message = useMessage();
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
formBtnLoading.value = true;
|
||||
ApplyRefund(params.value)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
isShowModal.value = false;
|
||||
emit('reloadTable');
|
||||
})
|
||||
.finally(() => {
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
isShowModal.value = false;
|
||||
}
|
||||
|
||||
function loadForm(value) {
|
||||
loading.value = true;
|
||||
|
||||
// 编辑
|
||||
View({ id: value.id })
|
||||
.then((res) => {
|
||||
params.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.formParams,
|
||||
(value) => {
|
||||
loadForm(value);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" title="充值记录" class="proCard">
|
||||
<n-tabs
|
||||
type="card"
|
||||
class="card-tabs"
|
||||
:value="defaultTab"
|
||||
animated
|
||||
@before-leave="handleBeforeLeave"
|
||||
>
|
||||
<n-tab-pane
|
||||
:name="item.key.toString()"
|
||||
:tab="item.label"
|
||||
v-for="item in dict.getOptionUnRef('orderStatus')"
|
||||
:key="item.key"
|
||||
>
|
||||
<List :type="defaultTab" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import List from './list.vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { loadOptions } from './model';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const dict = useDictStore();
|
||||
const router = useRouter();
|
||||
const defaultTab = ref('-1');
|
||||
|
||||
function handleBeforeLeave(tabName: string) {
|
||||
defaultTab.value = tabName;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (router.currentRoute.value.query?.type) {
|
||||
defaultTab.value = router.currentRoute.value.query.type as string;
|
||||
}
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="reloadTable"
|
||||
@reset="reloadTable"
|
||||
@keyup.enter="reloadTable"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:openChecked="true"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
type="error"
|
||||
@click="handleBatchDelete"
|
||||
:disabled="batchDeleteDisabled"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/order/delete'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="handleExport"
|
||||
class="min-left-space"
|
||||
v-if="hasPermission(['/order/export'])"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
|
||||
<ApplyRefund
|
||||
@reload-table="reloadTable"
|
||||
@update-show-modal="updateShowModal"
|
||||
:showModal="showModal"
|
||||
:formParams="formParams"
|
||||
/>
|
||||
|
||||
<AcceptRefund
|
||||
@reload-table="reloadTable"
|
||||
@update-show-modal="updateAcceptShowModal"
|
||||
:showModal="showAcceptModal"
|
||||
:formParams="formParams"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { List, Export, Delete } from '@/api/order';
|
||||
import { State, columns, schemas, newState } from './model';
|
||||
import { ExportOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import ApplyRefund from './applyRefund.vue';
|
||||
import AcceptRefund from './acceptRefund.vue';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
interface Props {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: '-1',
|
||||
});
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const searchFormRef = ref<any>({});
|
||||
const batchDeleteDisabled = ref(true);
|
||||
const checkedIds = ref([]);
|
||||
const showModal = ref(false);
|
||||
const showAcceptModal = ref(false);
|
||||
const formParams = ref<State>();
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 120,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
type: 'warning',
|
||||
label: '受理退款',
|
||||
onClick: handleAcceptRefund.bind(null, record),
|
||||
auth: ['/order/acceptRefund'],
|
||||
ifShow: () => {
|
||||
return record.status == 6;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'default',
|
||||
label: '申请退款',
|
||||
onClick: handleApplyRefund.bind(null, record),
|
||||
auth: ['/order/applyRefund'],
|
||||
ifShow: () => {
|
||||
return record.status == 4;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/order/delete'],
|
||||
ifShow: () => {
|
||||
return record.status == 5;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...searchFormRef.value?.formModel, ...res, ...{ status: props.type } });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
batchDeleteDisabled.value = rowKeys.length <= 0;
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleBatchDelete() {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?只有已关闭的订单才能被删除',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateShowModal(value) {
|
||||
showModal.value = value;
|
||||
}
|
||||
|
||||
function handleApplyRefund(record: Recordable) {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(record as State);
|
||||
}
|
||||
|
||||
function updateAcceptShowModal(value) {
|
||||
showAcceptModal.value = value;
|
||||
}
|
||||
|
||||
function handleAcceptRefund(record: Recordable) {
|
||||
showAcceptModal.value = true;
|
||||
formParams.value = newState(record as State);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reloadTable,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,154 @@
|
||||
import { ref } from 'vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { MemberSumma, renderOptionTag, renderPopoverMemberSumma } from '@/utils';
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
memberId: number;
|
||||
orderType: string;
|
||||
productId: number;
|
||||
orderSn: string;
|
||||
money: number;
|
||||
remark: string;
|
||||
payLogOutTradeNo: string;
|
||||
status: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
refundReason: string;
|
||||
rejectRefundReason: string;
|
||||
payLogPayType: string;
|
||||
memberBySumma?: null | MemberSumma;
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
id: 0,
|
||||
memberId: 0,
|
||||
orderType: '',
|
||||
productId: 0,
|
||||
orderSn: '',
|
||||
money: 0,
|
||||
payLogOutTradeNo: '',
|
||||
remark: '',
|
||||
status: 1,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
refundReason: '',
|
||||
rejectRefundReason: '',
|
||||
payLogPayType: '',
|
||||
};
|
||||
|
||||
export function newState(state: State | null): State {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(defaultState);
|
||||
}
|
||||
|
||||
const dict = useDictStore();
|
||||
export const rules = {};
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'complexMemberId',
|
||||
component: 'ComplexMemberPicker',
|
||||
label: '下单用户',
|
||||
componentProps: {
|
||||
placeholder: '请选择下单用户',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'orderSn',
|
||||
component: 'NInput',
|
||||
label: '业务单号',
|
||||
componentProps: {
|
||||
placeholder: '请输入业务订单号',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'payLogOutTradeNo',
|
||||
component: 'NInput',
|
||||
label: '商户单号',
|
||||
componentProps: {
|
||||
placeholder: '请输入商户订单号',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '业务订单号',
|
||||
key: 'orderSn',
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
title: '下单用户',
|
||||
key: 'memberId',
|
||||
width: 100,
|
||||
render(row: State) {
|
||||
return renderPopoverMemberSumma(row.memberBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '支付方式',
|
||||
key: 'payLogPayType',
|
||||
render(row) {
|
||||
return renderOptionTag('payType', row.payLogPayType);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '充值金额',
|
||||
key: 'money',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return '¥' + Number(row.money).toFixed(2);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '订单状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return renderOptionTag('orderStatus', row.status);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '商户订单号',
|
||||
key: 'payLogOutTradeNo',
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['payType', 'orderStatus', 'acceptRefundStatus']);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
:title="formValue.id > 0 ? '编辑CURD列表 #' + formValue.id : '添加CURD列表'"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-spin :show="loading" description="请稍候...">
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
:label-placement="settingStore.isMobile ? 'top' : 'left'"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-grid cols="1 s:1 m:2 l:2 xl:2 2xl:2" responsive="screen">
|
||||
<n-gi span="1">
|
||||
<n-form-item label="标题" path="title">
|
||||
<n-input placeholder="请输入标题" v-model:value="formValue.title" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="描述" path="description">
|
||||
<n-input type="textarea" placeholder="描述" v-model:value="formValue.description" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="2">
|
||||
<n-form-item label="内容" path="content">
|
||||
<Editor style="height: 450px" id="content" v-model:value="formValue.content" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="单图" path="image">
|
||||
<UploadImage :maxNumber="1" v-model:value="formValue.image" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="附件" path="attachfile">
|
||||
<UploadFile :maxNumber="1" v-model:value="formValue.attachfile" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="所在城市" path="cityId">
|
||||
<CitySelector v-model:value="formValue.cityId" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="显示开关" path="switch">
|
||||
<n-switch :unchecked-value="2" :checked-value="1" v-model:value="formValue.switch"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi span="1">
|
||||
<n-form-item label="排序" path="sort">
|
||||
<n-input-number placeholder="请输入排序" v-model:value="formValue.sort" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
</n-spin>
|
||||
</n-scrollbar>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" :disabled="!isFormValid" @click="confirmForm">
|
||||
确定
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { Edit, View, MaxSort } from '@/api/curdDemo';
|
||||
import { State, newState, rules } from './model';
|
||||
import Editor from '@/components/Editor/editor.vue';
|
||||
import UploadImage from '@/components/Upload/uploadImage.vue';
|
||||
import UploadFile from '@/components/Upload/uploadFile.vue';
|
||||
import CitySelector from '@/components/CitySelector/citySelector.vue';
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
const emit = defineEmits(['reloadTable']);
|
||||
const message = useMessage();
|
||||
const settingStore = useProjectSettingStore();
|
||||
const dict = useDictStore();
|
||||
const loading = ref(false);
|
||||
const showModal = ref(false);
|
||||
const formValue = ref<State>(newState(null));
|
||||
const formRef = ref<any>({});
|
||||
const formBtnLoading = ref(false);
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(840);
|
||||
});
|
||||
const isFormValid = ref(true);
|
||||
|
||||
// 提交表单
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
formBtnLoading.value = true;
|
||||
Edit(formValue.value)
|
||||
.then((_res) => {
|
||||
message.success('操作成功');
|
||||
closeForm();
|
||||
emit('reloadTable');
|
||||
})
|
||||
.finally(() => {
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭表单
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
// 打开模态框
|
||||
function openModal(state: State) {
|
||||
showModal.value = true;
|
||||
|
||||
// 新增
|
||||
if (!state || state.id < 1) {
|
||||
formValue.value = newState(state);
|
||||
|
||||
loading.value = true;
|
||||
MaxSort()
|
||||
.then((res) => {
|
||||
formValue.value.sort = res.sort;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 编辑
|
||||
loading.value = true;
|
||||
View({ id: state.id })
|
||||
.then((res) => {
|
||||
formValue.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="CURD列表">
|
||||
<!-- 这是由系统生成的CURD表格,你可以将此行注释改为表格的描述 -->
|
||||
</n-card>
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm ref="searchFormRef" @register="register" @submit="reloadTable" @reset="reloadTable" @keyup.enter="reloadTable">
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
<BasicTable ref="actionRef" openChecked :columns="columns" :request="loadDataTable" :row-key="(row) => row.id" :actionColumn="actionColumn" :scroll-x="scrollX" :resizeHeightOffset="-10000" :checked-row-keys="checkedIds" @update:checked-row-keys="handleOnCheckedRow">
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable" class="min-left-space" v-if="hasPermission(['/curdDemo/edit'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
添加
|
||||
</n-button>
|
||||
<n-button type="error" @click="handleBatchDelete" class="min-left-space" v-if="hasPermission(['/curdDemo/delete'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
批量删除
|
||||
</n-button>
|
||||
<n-button type="primary" @click="handleExport" class="min-left-space" v-if="hasPermission(['/curdDemo/export'])">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<ExportOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
导出
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Edit ref="editRef" @reloadTable="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, reactive, ref, computed, onMounted } from 'vue';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { List, Export, Delete, Status } from '@/api/curdDemo';
|
||||
import { PlusOutlined, ExportOutlined, DeleteOutlined } from '@vicons/antd';
|
||||
import { columns, schemas, loadOptions } from './model';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import Edit from './edit.vue';
|
||||
|
||||
const dict = useDictStore();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
const actionRef = ref();
|
||||
const searchFormRef = ref<any>({});
|
||||
const editRef = ref();
|
||||
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 216,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record: State) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
auth: ['/curdDemo/edit'],
|
||||
},
|
||||
|
||||
{
|
||||
label: '禁用',
|
||||
onClick: handleStatus.bind(null, record, 2),
|
||||
ifShow: () => {
|
||||
return record.status === 1;
|
||||
},
|
||||
auth: ['/curdDemo/status'],
|
||||
},
|
||||
{
|
||||
label: '启用',
|
||||
onClick: handleStatus.bind(null, record, 1),
|
||||
ifShow: () => {
|
||||
return record.status === 2;
|
||||
},
|
||||
auth: ['/curdDemo/status'],
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
auth: ['/curdDemo/delete'],
|
||||
},
|
||||
],
|
||||
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const scrollX = computed(() => {
|
||||
return adaTableScrollX(columns, actionColumn.width);
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
// 加载表格数据
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...searchFormRef.value?.formModel, ...res });
|
||||
};
|
||||
|
||||
// 更新选中的行
|
||||
function handleOnCheckedRow(rowKeys) {
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
// 重新加载表格数据
|
||||
function reloadTable() {
|
||||
actionRef.value?.reload();
|
||||
}
|
||||
|
||||
// 添加数据
|
||||
function addTable() {
|
||||
editRef.value.openModal(null);
|
||||
}
|
||||
|
||||
// 编辑数据
|
||||
function handleEdit(record: Recordable) {
|
||||
editRef.value.openModal(record);
|
||||
}
|
||||
|
||||
// 单个删除
|
||||
function handleDelete(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete(record).then((_res) => {
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
function handleBatchDelete() {
|
||||
if (checkedIds.value.length < 1){
|
||||
message.error('请至少选择一项要删除的数据');
|
||||
return;
|
||||
}
|
||||
|
||||
dialog.warning({
|
||||
title: '警告',
|
||||
content: '你确定要批量删除?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Delete({ id: checkedIds.value }).then((_res) => {
|
||||
checkedIds.value = [];
|
||||
message.success('删除成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 导出
|
||||
function handleExport() {
|
||||
message.loading('正在导出列表...', { duration: 1200 });
|
||||
Export(searchFormRef.value?.formModel);
|
||||
}
|
||||
|
||||
// 修改状态
|
||||
function handleStatus(record: Recordable, status: number) {
|
||||
Status({ id: record.id, status: status }).then((_res) => {
|
||||
message.success('设为' + dict.getLabel('sys_normal_disable', status) + '成功');
|
||||
setTimeout(() => {
|
||||
reloadTable();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadOptions();
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,280 @@
|
||||
import { h, ref } from 'vue';
|
||||
import { NSwitch } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { renderImage, renderFile, renderPopoverMemberSumma, MemberSumma } from '@/utils';
|
||||
import { Switch } from '@/api/curdDemo';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
|
||||
const dict = useDictStore();
|
||||
const { hasPermission } = usePermission();
|
||||
const $message = window['$message'];
|
||||
|
||||
export class State {
|
||||
|
||||
public id = 0;//ID
|
||||
public categoryId = null;//测试分类
|
||||
public title = '';//标题
|
||||
public description = '';//描述
|
||||
public content = '';//内容
|
||||
public image = '';//单图
|
||||
public attachfile = '';//附件
|
||||
public cityId = null;//所在城市
|
||||
public switch = 2;//显示开关
|
||||
public sort = 0;//排序
|
||||
public status = 1;//状态
|
||||
public createdBy = 0;//创建者
|
||||
public createdBySumma?: null | MemberSumma = null;//创建者摘要信息
|
||||
public updatedBy = 0;//更新者
|
||||
public updatedBySumma?: null | MemberSumma = null;//更新者摘要信息
|
||||
public deletedBy = 0;//删除者
|
||||
public deletedBySumma?: null | MemberSumma = null;//删除者摘要信息
|
||||
public createdAt = '';//创建时间
|
||||
public updatedAt = '';//修改时间
|
||||
public deletedAt = '';//删除时间
|
||||
constructor(state?: Partial<State>) {
|
||||
if (state) {
|
||||
Object.assign(this, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
if (state instanceof State) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return new State(state);
|
||||
}
|
||||
return new State();
|
||||
}
|
||||
|
||||
// 表单验证规则
|
||||
export const rules = {
|
||||
title: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '请输入标题',
|
||||
},
|
||||
description: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '请输入描述',
|
||||
},
|
||||
content: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '请输入内容',
|
||||
},
|
||||
sort: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'number',
|
||||
message: '请输入排序',
|
||||
},
|
||||
};
|
||||
|
||||
// 表格搜索表单
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'id',
|
||||
component: 'NInput',
|
||||
label: 'ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入ID',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
component: 'NInput',
|
||||
label: '标题',
|
||||
componentProps: {
|
||||
placeholder: '请输入标题',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
component: 'NInput',
|
||||
label: '描述',
|
||||
componentProps: {
|
||||
placeholder: '请输入描述',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdBy',
|
||||
component: 'NInput',
|
||||
label: '创建者',
|
||||
componentProps: {
|
||||
placeholder: '请输入ID|用户名|姓名|手机号',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'deletedBy',
|
||||
component: 'NInput',
|
||||
label: '删除者',
|
||||
componentProps: {
|
||||
placeholder: '请输入ID|用户名|姓名|手机号',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
component: 'NDatePicker',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
type: 'datetimerange',
|
||||
clearable: true,
|
||||
shortcuts: defRangeShortcuts(),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'testCategoryName',
|
||||
component: 'NInput',
|
||||
label: '关联分类',
|
||||
componentProps: {
|
||||
placeholder: '请输入关联分类',
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// 表格列
|
||||
export const columns = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
key: 'description',
|
||||
align: 'left',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: '单图',
|
||||
key: 'image',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row: State) {
|
||||
return renderImage(row.image);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '附件',
|
||||
key: 'attachfile',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
render(row: State) {
|
||||
return renderFile(row.attachfile);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '显示开关',
|
||||
key: 'switch',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
render(row: State) {
|
||||
return h(NSwitch, {
|
||||
value: row.switch === 1,
|
||||
checked: '开启',
|
||||
unchecked: '关闭',
|
||||
disabled: !hasPermission(['/curdDemo/switch']),
|
||||
onUpdateValue: function (e) {
|
||||
console.log('onUpdateValue e:' + JSON.stringify(e));
|
||||
row.switch = e ? 1 : 2;
|
||||
Switch({ id: row.id, key: 'switch', value: row.switch }).then((_res) => {
|
||||
$message.success('操作成功');
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
key: 'sort',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建者',
|
||||
key: 'createdBy',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
render(row: State) {
|
||||
return renderPopoverMemberSumma(row.createdBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '更新者',
|
||||
key: 'updatedBy',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
render(row: State) {
|
||||
return renderPopoverMemberSumma(row.updatedBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '删除者',
|
||||
key: 'deletedBy',
|
||||
align: 'left',
|
||||
width: -1,
|
||||
render(row: State) {
|
||||
return renderPopoverMemberSumma(row.deletedBySumma);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'createdAt',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
key: 'updatedAt',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '关联分类',
|
||||
key: 'testCategoryName',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['sys_normal_disable', 'testCategoryOption']);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, Ref } from 'vue';
|
||||
import { useECharts } from '@/hooks/web/useECharts';
|
||||
import { basicProps } from './props';
|
||||
|
||||
export default defineComponent({
|
||||
props: basicProps,
|
||||
setup() {
|
||||
const chartRef = ref<HTMLDivElement | null>(null);
|
||||
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
|
||||
|
||||
onMounted(() => {
|
||||
setOptions({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#019680',
|
||||
},
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: [
|
||||
'6:00',
|
||||
'7:00',
|
||||
'8:00',
|
||||
'9:00',
|
||||
'10:00',
|
||||
'11:00',
|
||||
'12:00',
|
||||
'13:00',
|
||||
'14:00',
|
||||
'15:00',
|
||||
'16:00',
|
||||
'17:00',
|
||||
'18:00',
|
||||
'19:00',
|
||||
'20:00',
|
||||
'21:00',
|
||||
'22:00',
|
||||
'23:00',
|
||||
],
|
||||
splitLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
type: 'solid',
|
||||
color: 'rgba(226,226,226,0.5)',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
max: 80000,
|
||||
splitNumber: 4,
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
splitArea: {
|
||||
show: true,
|
||||
areaStyle: {
|
||||
color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
|
||||
series: [
|
||||
{
|
||||
smooth: true,
|
||||
data: [
|
||||
111, 222, 4000, 18000, 33333, 55555, 66666, 33333, 14000, 36000, 66666, 44444,
|
||||
22222, 11111, 4000, 2000, 500, 333, 222, 111,
|
||||
],
|
||||
type: 'line',
|
||||
areaStyle: {},
|
||||
itemStyle: {
|
||||
color: '#5ab1ef',
|
||||
},
|
||||
},
|
||||
{
|
||||
smooth: true,
|
||||
data: [
|
||||
33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201,
|
||||
390, 198, 60, 30, 22, 11,
|
||||
],
|
||||
type: 'line',
|
||||
areaStyle: {},
|
||||
itemStyle: {
|
||||
color: '#019680',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
return { chartRef };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
CaretUpOutlined,
|
||||
CaretDownOutlined,
|
||||
UsergroupAddOutlined,
|
||||
BarChartOutlined,
|
||||
ShoppingCartOutlined,
|
||||
AccountBookOutlined,
|
||||
CreditCardOutlined,
|
||||
MailOutlined,
|
||||
TagsOutlined,
|
||||
SettingOutlined,
|
||||
} from '@vicons/antd';
|
||||
|
||||
export default {
|
||||
CaretUpOutlined,
|
||||
CaretDownOutlined,
|
||||
UsergroupAddOutlined,
|
||||
BarChartOutlined,
|
||||
ShoppingCartOutlined,
|
||||
AccountBookOutlined,
|
||||
CreditCardOutlined,
|
||||
MailOutlined,
|
||||
TagsOutlined,
|
||||
SettingOutlined,
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="mt-4">
|
||||
<NRow :gutter="24">
|
||||
<NCol :span="24">
|
||||
<n-card content-style="padding: 0;" :bordered="false">
|
||||
<n-tabs
|
||||
type="line"
|
||||
size="large"
|
||||
:tabs-padding="20"
|
||||
pane-style="padding: 20px;"
|
||||
default-value="FluxTrend"
|
||||
>
|
||||
<n-tab-pane name="FluxTrend" :tab="t('流量消耗趋势')">
|
||||
<FluxTrend />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="VisitAmount" :tab="t('客户端访问量')">
|
||||
<VisitAmount />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</NCol>
|
||||
</NRow>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import FluxTrend from './FluxTrend.vue';
|
||||
import VisitAmount from './VisitAmount.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { FluxTrend, VisitAmount },
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div ref="chartRef" :style="{ height, width }"></div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, onMounted, ref, Ref } from 'vue';
|
||||
import { useECharts } from '@/hooks/web/useECharts';
|
||||
import { basicProps } from './props';
|
||||
|
||||
export default defineComponent({
|
||||
props: basicProps,
|
||||
setup() {
|
||||
const chartRef = ref<HTMLDivElement | null>(null);
|
||||
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
|
||||
|
||||
onMounted(() => {
|
||||
setOptions({
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: '#019680',
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [
|
||||
'1月',
|
||||
'2月',
|
||||
'3月',
|
||||
'4月',
|
||||
'5月',
|
||||
'6月',
|
||||
'7月',
|
||||
'8月',
|
||||
'9月',
|
||||
'10月',
|
||||
'11月',
|
||||
'12月',
|
||||
],
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
max: 8000,
|
||||
splitNumber: 4,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, 3200, 4800],
|
||||
type: 'bar',
|
||||
barMaxWidth: 80,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
return { chartRef };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { PropType } from 'vue';
|
||||
|
||||
export interface BasicProps {
|
||||
width: string;
|
||||
height: string;
|
||||
}
|
||||
|
||||
export const basicProps = {
|
||||
width: {
|
||||
type: String as PropType<string>,
|
||||
default: '100%',
|
||||
},
|
||||
height: {
|
||||
type: String as PropType<string>,
|
||||
default: '280px',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="mt-2">
|
||||
<Company v-if="userStore.isCompanyDept" />
|
||||
<Tenant v-else-if="userStore.isTenantDept" />
|
||||
<Merchant v-else-if="userStore.isMerchantDept" />
|
||||
<User v-else-if="userStore.isUserDept" />
|
||||
<template v-else>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="默认首页">
|
||||
部门类型未配置首页:{{ userStore.info?.deptType }}
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import Company from './console_company.vue';
|
||||
import Merchant from './console_merchant.vue';
|
||||
import User from './console_user.vue';
|
||||
import Tenant from './console_tenant.vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
</script>
|
||||
@@ -0,0 +1,343 @@
|
||||
<template>
|
||||
<div class="console">
|
||||
<!--数据卡片-->
|
||||
<n-grid cols="1 s:2 m:3 l:4 xl:4 2xl:4" responsive="screen" :x-gap="12" :y-gap="8">
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
:title="t('卡板量')"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="success">{{ t('日') }}</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else :startVal="1" :endVal="visits.dayVisits" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
{{ t('日同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="visits.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
{{ t('周同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="visits.decline" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" text :repeat="2" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> {{ t('总卡板量:') }} </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" :endVal="visits.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
:title="t('激活卡板')"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="info">{{ t('周') }}</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo
|
||||
v-else
|
||||
prefix="¥"
|
||||
:startVal="1"
|
||||
:endVal="saleroom.weekSaleroom"
|
||||
class="text-3xl"
|
||||
/>
|
||||
</div>
|
||||
<div class="py-2 px-2 flex justify-between">
|
||||
<div class="text-sn flex-1">
|
||||
<n-progress
|
||||
type="line"
|
||||
:percentage="saleroom.degree"
|
||||
:indicator-placement="'inside'"
|
||||
processing
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> {{ t('总激活卡板:') }} </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" :endVal="saleroom.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
:title="t('代理商')"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="warning">{{ t('周') }}</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else :startVal="1" :endVal="orderLarge.weekLarge" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
{{ t('日同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
{{ t('周同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.rise" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> {{ t('总代理商量:') }} </div>
|
||||
<div class="text-sn">
|
||||
<CountTo :startVal="1" suffix="%" :endVal="orderLarge.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<NCard
|
||||
:title="t('提现佣金')"
|
||||
:segmented="{ content: true, footer: true }"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-tag type="error">{{ t('月') }}</n-tag>
|
||||
</template>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<CountTo v-else prefix="¥" :startVal="1" :endVal="volume.weekLarge" class="text-3xl" />
|
||||
</div>
|
||||
<div class="py-1 px-1 flex justify-between">
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
{{ t('月同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="volume.rise" />
|
||||
<n-icon size="12" color="#00ff6f">
|
||||
<CaretUpOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-sn">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
{{ t('月同比') }}
|
||||
<CountTo :startVal="1" suffix="%" :endVal="volume.decline" />
|
||||
<n-icon size="12" color="#ffde66">
|
||||
<CaretDownOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<n-skeleton v-if="loading" :width="100" size="medium" />
|
||||
<template v-else>
|
||||
<div class="text-sn"> {{ t('总提现额:') }} </div>
|
||||
<div class="text-sn">
|
||||
<CountTo prefix="¥" :startVal="1" :endVal="volume.amount" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<!--导航卡片-->
|
||||
<div class="mt-4">
|
||||
<n-grid cols="1 s:2 m:3 l:8 xl:8 2xl:8" responsive="screen" :x-gap="16" :y-gap="8">
|
||||
<n-grid-item v-for="(item, index) in iconList" :key="index" @click="item.eventObject || {}">
|
||||
<NCard
|
||||
content-style="padding-top: 0;"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
v-on="item.eventObject || {}"
|
||||
>
|
||||
<template #footer>
|
||||
<n-skeleton v-if="loading" size="medium" />
|
||||
<div class="cursor-pointer" v-else>
|
||||
<p class="flex justify-center">
|
||||
<span>
|
||||
<n-icon :size="item.size" class="flex-1" :color="item.color">
|
||||
<component :is="item.icon" />
|
||||
</n-icon>
|
||||
</span>
|
||||
</p>
|
||||
<p class="flex justify-center"
|
||||
><span>{{ item.title }}</span></p
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</NCard>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
</div>
|
||||
|
||||
<!--访问量 | 流量趋势-->
|
||||
<VisiTab />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { getConsoleInfo } from '@/api/dashboard/console';
|
||||
import VisiTab from './components/VisiTab.vue';
|
||||
import { CountTo } from '@/components/CountTo';
|
||||
import {
|
||||
CaretUpOutlined,
|
||||
CaretDownOutlined,
|
||||
UsergroupAddOutlined,
|
||||
BarChartOutlined,
|
||||
ShoppingCartOutlined,
|
||||
AccountBookOutlined,
|
||||
CreditCardOutlined,
|
||||
MailOutlined,
|
||||
TagsOutlined,
|
||||
SettingOutlined,
|
||||
} from '@vicons/antd';
|
||||
|
||||
const loading = ref(true);
|
||||
const visits = ref<any>({});
|
||||
const saleroom = ref<any>({});
|
||||
const orderLarge = ref<any>({});
|
||||
const volume = ref({});
|
||||
const router = useRouter();
|
||||
|
||||
// 图标列表
|
||||
const iconList = computed(() => {
|
||||
return [
|
||||
{
|
||||
icon: UsergroupAddOutlined,
|
||||
size: '32',
|
||||
title: t('用户'),
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => router.push({ name: 'user' }),
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: BarChartOutlined,
|
||||
size: '32',
|
||||
title: t('分析'),
|
||||
color: '#69c0ff',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: ShoppingCartOutlined,
|
||||
size: '32',
|
||||
title: t('商品'),
|
||||
color: '#ff9c6e',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: AccountBookOutlined,
|
||||
size: '32',
|
||||
title: t('订单'),
|
||||
color: '#b37feb',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: CreditCardOutlined,
|
||||
size: '32',
|
||||
title: t('票据'),
|
||||
color: '#ffd666',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: MailOutlined,
|
||||
size: '32',
|
||||
title: t('消息'),
|
||||
color: '#5cdbd3',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: TagsOutlined,
|
||||
size: '32',
|
||||
title: t('标签'),
|
||||
color: '#ff85c0',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: SettingOutlined,
|
||||
size: '32',
|
||||
title: t('配置'),
|
||||
color: '#ffc069',
|
||||
eventObject: {
|
||||
click: () => {},
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await getConsoleInfo();
|
||||
visits.value = data.visits;
|
||||
saleroom.value = data.saleroom;
|
||||
orderLarge.value = data.orderLarge;
|
||||
volume.value = data.volume;
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="商户首页"> 这是商户的首页,如果需要,你可以定制TA </n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="租户首页"> 这是租户的首页,如果需要,你可以定制TA </n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="用户首页">
|
||||
这是普通用户的首页,如果需要,你可以定制TA
|
||||
</n-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
@@ -0,0 +1,328 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="mt-2">
|
||||
<n-card :bordered="false" title="工作台">
|
||||
<n-grid cols="2 s:1 m:1 l:2 xl:2 2xl:2" responsive="screen">
|
||||
<n-gi>
|
||||
<div class="flex items-center">
|
||||
<div>
|
||||
<n-avatar circle :size="64" :src="schoolboy" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="px-4 text-xl">早安,{{ username }},开始您一天的工作吧!</p>
|
||||
<p class="px-4 text-gray-400">今日阴转大雨,15℃ - 25℃,出门记得带伞哦。</p>
|
||||
</div>
|
||||
</div>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<div class="flex justify-end w-full">
|
||||
<div class="flex flex-1 flex-col justify-center text-right">
|
||||
<span class="text-secondary">项目数</span>
|
||||
<span class="text-2xl">16</span>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col justify-center text-right">
|
||||
<span class="text-secondary">待办</span>
|
||||
<span class="text-2xl">3/15</span>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col justify-center text-right">
|
||||
<span class="text-secondary">消息</span>
|
||||
<span class="text-2xl">35</span>
|
||||
</div>
|
||||
</div>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</n-card>
|
||||
</div>
|
||||
<n-grid class="mt-4" cols="2 s:1 m:1 l:2 xl:2 2xl:2" responsive="screen" :x-gap="12" :y-gap="9">
|
||||
<n-gi>
|
||||
<n-card
|
||||
:segmented="{ content: true }"
|
||||
content-style="padding: 0;"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
title="项目"
|
||||
>
|
||||
<div class="flex flex-wrap project-card">
|
||||
<n-card
|
||||
size="small"
|
||||
class="cursor-pointer project-card-item ms:w-1/2 md:w-1/3"
|
||||
hoverable
|
||||
>
|
||||
<div class="flex">
|
||||
<span>
|
||||
<n-icon size="30">
|
||||
<GithubOutlined />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lg ml-4">Github</span>
|
||||
</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400">
|
||||
是一个面向开源及私有软件项目的托管平台。
|
||||
</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 开源君,2021-07-04</div>
|
||||
</n-card>
|
||||
<n-card
|
||||
size="small"
|
||||
class="cursor-pointer project-card-item ms:w-1/2 md:w-1/3"
|
||||
hoverable
|
||||
>
|
||||
<div class="flex">
|
||||
<span>
|
||||
<n-icon size="30" color="#42b983">
|
||||
<LogoVue />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lg ml-4">Vue</span>
|
||||
</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 渐进式 JavaScript 框架</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 学不动也要学,2021-07-04</div>
|
||||
</n-card>
|
||||
<n-card
|
||||
size="small"
|
||||
class="cursor-pointer project-card-item ms:w-1/2 md:w-1/3"
|
||||
hoverable
|
||||
>
|
||||
<div class="flex">
|
||||
<span>
|
||||
<n-icon size="30" color="#e44c27">
|
||||
<Html5Outlined />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lg ml-4">Html5</span>
|
||||
</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> HTML5是互联网的下一代标准。</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 撸码也是一种艺术 2021-04-01</div>
|
||||
</n-card>
|
||||
<n-card
|
||||
size="small"
|
||||
class="cursor-pointer project-card-item ms:w-1/2 md:w-1/3"
|
||||
hoverable
|
||||
>
|
||||
<div class="flex">
|
||||
<span>
|
||||
<n-icon size="30" color="#dd0031">
|
||||
<LogoAngular />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lg ml-4">Angular</span>
|
||||
</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 现代 Web 开发平台,百万粉丝热捧。</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 铁粉君 2021-07-04。</div>
|
||||
</n-card>
|
||||
<n-card
|
||||
size="small"
|
||||
class="cursor-pointer project-card-item ms:w-1/2 md:w-1/3"
|
||||
hoverable
|
||||
>
|
||||
<div class="flex">
|
||||
<span>
|
||||
<n-icon size="30" color="#61dafb">
|
||||
<LogoReact />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lg ml-4">React</span>
|
||||
</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 用于构建用户界面的 JavaScript 库。</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 技术牛 2021-07-04。</div>
|
||||
</n-card>
|
||||
<n-card
|
||||
size="small"
|
||||
class="cursor-pointer project-card-item ms:w-1/2 md:w-1/3"
|
||||
hoverable
|
||||
>
|
||||
<div class="flex">
|
||||
<span>
|
||||
<n-icon size="30">
|
||||
<LogoJavascript />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lg ml-4">Js</span>
|
||||
</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 路是走出来的,而不是空想出来的。</div>
|
||||
<div class="flex mt-2 h-10 text-gray-400"> 架构组 2021-07-04</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:segmented="{ content: true }"
|
||||
content-style="padding-top: 0;padding-bottom: 0;"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
title="动态"
|
||||
class="mt-4"
|
||||
>
|
||||
<template #header-extra><a href="javascript:;">更多</a></template>
|
||||
<n-list>
|
||||
<n-list-item>
|
||||
<template #prefix>
|
||||
<n-avatar circle :size="40" :src="schoolboy" />
|
||||
</template>
|
||||
<n-thing :title="username + ',刚才把工作台页面随便写了一些,凑合能看了!'">
|
||||
<template #description
|
||||
><p class="text-xs text-gray-500">2021-07-04 22:37:16</p></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<template #prefix>
|
||||
<n-avatar circle :size="40" :src="schoolboy" />
|
||||
</template>
|
||||
<n-thing :title="username + ' 在 开源组 创建了项目 hotGo?'">
|
||||
<template #description
|
||||
><p class="text-xs text-gray-500">2021-07-04 09:37:16</p></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<template #prefix>
|
||||
<n-avatar circle :size="40" :src="schoolboy" />
|
||||
</template>
|
||||
<n-thing title="@It界风清扬,向hotGo提交了一个bug,抽时间看看吧!">
|
||||
<template #description
|
||||
><p class="text-xs text-gray-500">2021-07-04 22:37:16</p></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<template #prefix>
|
||||
<n-avatar circle :size="40" :src="schoolboy" />
|
||||
</template>
|
||||
<n-thing title="技术部那几位童鞋,再次警告,不要摸鱼,不要摸鱼,不要摸鱼啦!">
|
||||
<template #description
|
||||
><p class="text-xs text-gray-500">2021-07-04 09:37:16</p></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<template #prefix>
|
||||
<n-avatar circle :size="40" :src="schoolboy" />
|
||||
</template>
|
||||
<n-thing title="上班不摸鱼,和咸鱼有什么区别(这话真不是我说的哈)!">
|
||||
<template #description
|
||||
><p class="text-xs text-gray-500">2021-07-04 20:37:16</p></template
|
||||
>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<template #prefix>
|
||||
<n-avatar circle :size="40" :src="schoolboy" />
|
||||
</template>
|
||||
<n-thing title="页面切换其实也支持缓存,只是加了过度效果,看起来像是重新渲染了">
|
||||
<template #description>
|
||||
<p class="text-gray-400">
|
||||
<n-input type="text" placeholder="不信,输点文字试试" />
|
||||
</p>
|
||||
</template>
|
||||
</n-thing>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</n-card>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-card
|
||||
:segmented="{ content: true }"
|
||||
content-style="padding: 0;"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
title="快捷操作"
|
||||
>
|
||||
<div class="flex flex-wrap project-card">
|
||||
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
|
||||
<div class="flex flex-col justify-center text-gray-500">
|
||||
<span class="text-center">
|
||||
<n-icon size="30" color="#68c755">
|
||||
<DashboardOutlined />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lx text-center">主控台</span>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
|
||||
<div class="flex flex-col justify-center text-gray-500">
|
||||
<span class="text-center">
|
||||
<n-icon size="30" color="#fab251">
|
||||
<ProfileOutlined />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lx text-center">列表</span>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
|
||||
<div class="flex flex-col justify-center text-gray-500">
|
||||
<span class="text-center">
|
||||
<n-icon size="30" color="#1890ff">
|
||||
<FileProtectOutlined />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lx text-center">表单</span>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
|
||||
<div class="flex flex-col justify-center text-gray-500">
|
||||
<span class="text-center">
|
||||
<n-icon size="30" color="#f06b96">
|
||||
<ApartmentOutlined />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lx text-center">权限管理</span>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
|
||||
<div class="flex flex-col justify-center text-gray-500">
|
||||
<span class="text-center">
|
||||
<n-icon size="30" color="#7238d1">
|
||||
<SettingOutlined />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lx text-center">系统管理</span>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card size="small" class="cursor-pointer project-card-item" hoverable>
|
||||
<div class="flex flex-col justify-center text-gray-500">
|
||||
<span class="text-center">
|
||||
<n-icon size="30" color="">
|
||||
<DashboardOutlined />
|
||||
</n-icon>
|
||||
</span>
|
||||
<span class="text-lx text-center">主控台</span>
|
||||
</div>
|
||||
</n-card>
|
||||
</div>
|
||||
</n-card>
|
||||
<n-card :segmented="{ content: true }" :bordered="false" size="small" class="mt-4">
|
||||
<img src="~@/assets/images/Business.svg" class="w-full" />
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import schoolboy from '@/assets/images/schoolboy.png';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import {
|
||||
GithubOutlined,
|
||||
DashboardOutlined,
|
||||
ProfileOutlined,
|
||||
FileProtectOutlined,
|
||||
SettingOutlined,
|
||||
ApartmentOutlined,
|
||||
Html5Outlined,
|
||||
} from '@vicons/antd';
|
||||
import { LogoVue, LogoAngular, LogoReact, LogoJavascript } from '@vicons/ionicons5';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const username = userStore.info.username;
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.project-card {
|
||||
margin-right: -6px;
|
||||
|
||||
&-item {
|
||||
margin: -1px;
|
||||
width: 33.333333%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,326 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="插件管理">
|
||||
在这里可以管理和创建插件模板,开发独立、临时性、工具类型的功能时推荐使用插件化开发。
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@register="register"
|
||||
@submit="handleSubmit"
|
||||
@reset="handleReset"
|
||||
ref="searchFormRef"
|
||||
>
|
||||
<template #statusSlot="{ model, field }">
|
||||
<n-input v-model:value="model[field]" />
|
||||
</template>
|
||||
</BasicForm>
|
||||
|
||||
<BasicTable
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1090"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
创建新插件
|
||||
</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
title="创建新插件"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-alert :show-icon="false" type="info">
|
||||
注意:插件创建成功后会在服务端对应的项目目录中生成插件模块文件,并自动注册到当前项目中。
|
||||
</n-alert>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="80"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="插件标签" path="label">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.label" />
|
||||
<template #feedback>显示在插件列表中的模块名称. 不要超过20个字符</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="插件包名" path="name">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.name" />
|
||||
<template #feedback>
|
||||
系统按照此名称创建和查找插件定义, 只能英文和下划线组成,建议全部小写,例如:hgexample
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="功能分组" path="group">
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="dict.getOptionUnRef('addonsGroupOptions')"
|
||||
v-model:value="formParams.group"
|
||||
:on-update:value="onUpdateValueGroup"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="扩展功能" path="extend">
|
||||
<n-checkbox-group v-model:value="formParams.extend">
|
||||
<n-space item-style="display: flex;">
|
||||
<n-checkbox
|
||||
:value="option.value"
|
||||
:label="option.label"
|
||||
v-for="option in dict.getOptionUnRef('addonsExtend')"
|
||||
:key="option"
|
||||
/>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="版本" path="version">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.version" />
|
||||
<template #feedback>此版本号用于插件的版本更新</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="简介" path="brief">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.brief" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="使用说明" path="description">
|
||||
<n-input
|
||||
type="textarea"
|
||||
placeholder="使用说明"
|
||||
v-model:value="formParams.description"
|
||||
/>
|
||||
<template #feedback>详细介绍插件的功能和使用方法</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="作者" path="author">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.author" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="() => (showModal = false)">取消</n-button>
|
||||
<n-button type="info" :loading="formBtnLoading" @click="confirmForm">确认创建</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { NIcon, useMessage, useDialog, useNotification } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { List, Build, UnInstall, Install, Upgrade } from '@/api/develop/addons';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { newState, schemas, columns, loadOptions } from './model';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const dict = useDictStore();
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
const notification = useNotification();
|
||||
const showModal = ref(false);
|
||||
const formBtnLoading = ref(false);
|
||||
const formRef: any = ref(null);
|
||||
const actionRef = ref();
|
||||
const formParams = ref<any>();
|
||||
const checkedIds = ref([]);
|
||||
const searchFormRef = ref<any>();
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth();
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
render(record) {
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '安装模块',
|
||||
onClick: handleInstall.bind(null, record),
|
||||
ifShow: () => {
|
||||
return record.installStatus !== 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'warning',
|
||||
label: '更新配置',
|
||||
onClick: handleUpgrade.bind(null, record),
|
||||
ifShow: () => {
|
||||
return record.installStatus === 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'error',
|
||||
label: '卸载模块',
|
||||
onClick: handleUnInstall.bind(null, record),
|
||||
ifShow: () => {
|
||||
return record.installStatus === 1;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [register, {}] = useForm({
|
||||
gridProps: { cols: '1 s:1 m:2 l:3 xl:4 2xl:4' },
|
||||
labelWidth: 80,
|
||||
schemas,
|
||||
});
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
checkedIds.value = rowKeys;
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleInstall(record: Recordable) {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '你确定要安装 【' + record.label + '】 模块?安装成功后需要重启服务才能生效!',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Install(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleUpgrade(record: Recordable) {
|
||||
dialog.warning({
|
||||
title: '提示',
|
||||
content: '你确定要更新 【' + record.label + '】 模块?更新成功后需要重启服务才能生效!',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Upgrade(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleUnInstall(record: Recordable) {
|
||||
dialog.error({
|
||||
title: '提示',
|
||||
content: '你确定要卸载 【' + record.label + '】 模块?卸载成功后需要重启服务才能生效!',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
UnInstall(record).then((_res) => {
|
||||
message.success('操作成功');
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleSubmit(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function handleReset(_values: Recordable) {
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
function addTable() {
|
||||
showModal.value = true;
|
||||
formParams.value = newState(null);
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formBtnLoading.value = true;
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content:
|
||||
'你确定要提交生成吗?热编译生成后如果列表自动刷新没有出现新插件请等几秒刷新即可。否则请手动重启服务后刷新页面!',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
Build(formParams.value).then((_res) => {
|
||||
showModal.value = false;
|
||||
buildSuccessNotify();
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
formBtnLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function onUpdateValueGroup(value) {
|
||||
formParams.value.group = value;
|
||||
}
|
||||
|
||||
function buildSuccessNotify() {
|
||||
let count = 10;
|
||||
const n = notification.success({
|
||||
title: '插件构建成功',
|
||||
content: `如果你使用的热编译,页面将在 ${count} 秒后自动刷新插件列表即可生效。否则请手动重启服务后刷新页面!`,
|
||||
duration: 10000,
|
||||
closable: false,
|
||||
onAfterEnter: () => {
|
||||
const minusCount = () => {
|
||||
count--;
|
||||
n.content = `如果你使用的热编译,页面将在 ${count} 秒后自动刷新插件列表即可生效。否则请手动重启服务后刷新页面!`;
|
||||
if (count > 0) {
|
||||
window.setTimeout(minusCount, 1000);
|
||||
}
|
||||
};
|
||||
window.setTimeout(minusCount, 1000);
|
||||
},
|
||||
onAfterLeave: () => {
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,179 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { h, ref } from 'vue';
|
||||
import { fallbackSrc } from '@/utils/hotgo';
|
||||
import { isUrl } from '@/utils/is';
|
||||
import { NIcon, NIconWrapper, NImage, NTag } from 'naive-ui';
|
||||
import { getIconComponent } from '@/utils/icons';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
|
||||
const dict = useDictStore();
|
||||
|
||||
export const genInfoObj = {
|
||||
label: '',
|
||||
name: '',
|
||||
group: 1,
|
||||
extend: ['resourcePublic', 'resourceTemplate'],
|
||||
version: 'v1.0.0',
|
||||
brief: '',
|
||||
description: '',
|
||||
author: '',
|
||||
};
|
||||
|
||||
export function newState(state) {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(genInfoObj);
|
||||
}
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: '图标',
|
||||
key: 'logo',
|
||||
width: 80,
|
||||
render(row) {
|
||||
if (isUrl(row.logo)) {
|
||||
return h(NImage, {
|
||||
width: 48,
|
||||
height: 48,
|
||||
src: row.logo,
|
||||
fallbackSrc: fallbackSrc(),
|
||||
style: {
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
'max-width': '100%',
|
||||
'max-height': '100%',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return h(
|
||||
NIconWrapper,
|
||||
{
|
||||
size: 48,
|
||||
borderRadius: 8,
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
h(
|
||||
NIcon,
|
||||
{
|
||||
size: 36,
|
||||
style: {
|
||||
marginTop: '-8px',
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => h(getIconComponent(row.logo)),
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '模块名称',
|
||||
key: 'name',
|
||||
width: 120,
|
||||
render(row) {
|
||||
return h('div', {
|
||||
innerHTML:
|
||||
'<div >' + row.label + '<br><span style="opacity: 0.8;">' + row.name + '</span></div>',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '作者',
|
||||
key: 'author',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '分组',
|
||||
key: 'groupName',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: 'info',
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () => row.groupName,
|
||||
}
|
||||
);
|
||||
},
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '简介',
|
||||
key: 'brief',
|
||||
render(row) {
|
||||
return row.brief;
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '详细描述',
|
||||
key: 'description',
|
||||
width: 300,
|
||||
render(row) {
|
||||
return h('p', { id: 'app' }, [
|
||||
h('div', {
|
||||
innerHTML: '<div style="white-space: pre-wrap">' + row.description + '</div>',
|
||||
}),
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '版本',
|
||||
key: 'version',
|
||||
width: 100,
|
||||
},
|
||||
];
|
||||
|
||||
export const schemas = ref<FormSchema[]>([
|
||||
{
|
||||
field: 'name',
|
||||
component: 'NInput',
|
||||
label: '模块名称',
|
||||
componentProps: {
|
||||
placeholder: '请输入模块名称或标签',
|
||||
onInput: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
rules: [{ trigger: ['blur'] }],
|
||||
},
|
||||
{
|
||||
field: 'group',
|
||||
component: 'NSelect',
|
||||
label: '分组',
|
||||
componentProps: {
|
||||
placeholder: '请选择分组',
|
||||
options: dict.getOption('addonsGroupOptions'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '安装状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: dict.getOption('addonsInstallStatus'),
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['addonsGroupOptions', 'addonsInstallStatus', 'addonsExtend']);
|
||||
}
|
||||
@@ -0,0 +1,614 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-spin :show="bodyShow" description="请稍候...">
|
||||
<n-card
|
||||
:bordered="true"
|
||||
title="基本设置"
|
||||
class="proCard mt-2"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
>
|
||||
<n-form ref="formRef" :model="formValue">
|
||||
<n-row :gutter="24">
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item label="生成类型" path="title">
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.genType"
|
||||
v-model:value="formValue.genType"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item label="实体命名" path="varName">
|
||||
<n-input placeholder="请输入" v-model:value="formValue.varName" />
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item
|
||||
label="数据库"
|
||||
path="dbName"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.db"
|
||||
v-model:value="formValue.dbName"
|
||||
@update:value="handleDbUpdateValue"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item
|
||||
label="数据库表"
|
||||
path="tableName"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="tablesLoading"
|
||||
placeholder="请选择"
|
||||
:options="tablesOption"
|
||||
v-model:value="formValue.tableName"
|
||||
@update:value="handleTableUpdateValue"
|
||||
:disabled="formValue.dbName === ''"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<!-- 树表-->
|
||||
<template v-if="formValue.genType == 11">
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item path="title">
|
||||
<template #label>
|
||||
<div class="flex flex-row items-end"
|
||||
>树名称字段
|
||||
<n-tooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<n-button strong text>
|
||||
<template #icon>
|
||||
<n-icon size="15" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
</template>
|
||||
树节点的显示名称字段名, 如:`title`
|
||||
</n-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="columnsLoading"
|
||||
placeholder="请选择"
|
||||
:options="columnsOption"
|
||||
v-model:value="formValue.options.tree.titleColumn"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item label="树表格样式" path="styleType">
|
||||
<n-radio-group v-model:value="formValue.options.tree.styleType" name="styleType">
|
||||
<n-radio
|
||||
v-for="status in selectList.treeStyleType"
|
||||
:value="status.value"
|
||||
:label="status.label"
|
||||
>{{ status.label }}</n-radio
|
||||
>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</template>
|
||||
|
||||
<!-- 树表-->
|
||||
|
||||
<n-col :span="18">
|
||||
<n-form-item
|
||||
label="表格头部按钮组"
|
||||
path="tableName"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-checkbox-group v-model:value="formValue.options.headOps">
|
||||
<n-space item-style="display: flex;">
|
||||
<n-checkbox value="add" label="新增表单按钮" />
|
||||
<n-checkbox value="batchDel" label="批量删除按钮" />
|
||||
<n-checkbox value="export" label="导出按钮" />
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="24">
|
||||
<n-form-item
|
||||
label="表格列操作"
|
||||
path="columnOps"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-checkbox-group v-model:value="formValue.options.columnOps">
|
||||
<n-space item-style="display: flex;">
|
||||
<n-checkbox value="edit" label="编辑" />
|
||||
<n-checkbox value="status" label="状态修改" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>主表中存在`status`字段时才会生效</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="del" label="删除" />
|
||||
<n-checkbox value="view" label="详情页" />
|
||||
<n-checkbox value="check" label="开启勾选列" />
|
||||
<n-checkbox value="notFilterAuth" label="不过滤权限" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>系统默认会过滤权限,勾选后强制不开启权限验证</span>
|
||||
</n-popover>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="24">
|
||||
<n-form-item
|
||||
label="高级设置"
|
||||
path="autoOps"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-checkbox-group v-model:value="formValue.options.autoOps">
|
||||
<n-space item-style="display: flex;">
|
||||
<n-checkbox value="genMenuPermissions" label="生成菜单权限" />
|
||||
<n-checkbox value="runDao" label="生成前运行 [gf gen dao]" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>如果你选择的表已经生成过dao相关代码,取消勾选可减少生成时间</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="runService" label="生成后运行 [gf gen service]" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>如果是插件模块,勾选后也会自动在对应插件下运行service相关代码生成</span>
|
||||
</n-popover>
|
||||
<n-checkbox
|
||||
value="genFuncDict"
|
||||
label="生成字典选项"
|
||||
@click="handleCheckboxGenFuncDict"
|
||||
/>
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>将表数据生成为数据选项,并注册到内置的方法字典</span>
|
||||
</n-popover>
|
||||
<n-checkbox value="forcedCover" label="强制覆盖" />
|
||||
<n-popover trigger="hover">
|
||||
<template #trigger>
|
||||
<n-icon size="15" class="tips-help-icon" color="#2d8cf0">
|
||||
<QuestionCircleOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<span>强制覆盖所有需要生成的代码文件,但不包含菜单SQL文件</span>
|
||||
</n-popover>
|
||||
</n-space>
|
||||
</n-checkbox-group>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col
|
||||
:span="6"
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item path="pid">
|
||||
<template #label>
|
||||
<span>上级菜单</span>
|
||||
<n-button class="ml-2" text type="primary" strong @click="handleAddMenu"
|
||||
>菜单管理</n-button
|
||||
>
|
||||
</template>
|
||||
<n-tree-select
|
||||
:options="optionMenuTree"
|
||||
:value="formValue.options.menu.pid"
|
||||
@update:value="handleUpdateMenuPid"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col
|
||||
:span="6"
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item label="菜单名称" path="tableComment">
|
||||
<n-input placeholder="请输入" v-model:value="formValue.tableComment" />
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col
|
||||
:span="6"
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item label="菜单图标" path="menuIcon">
|
||||
<IconSelector style="width: 100%" v-model:value="formValue.options.menu.icon" />
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col
|
||||
:span="6"
|
||||
style="min-width: 200px"
|
||||
v-show="formValue.options?.autoOps?.includes('genMenuPermissions')"
|
||||
>
|
||||
<n-form-item label="菜单排序" path="menuIcon">
|
||||
<n-input-number
|
||||
style="width: 100%"
|
||||
placeholder="请输入"
|
||||
v-model:value="formValue.options.menu.sort"
|
||||
clearable
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card
|
||||
:bordered="true"
|
||||
title="关联表设置"
|
||||
class="proCard mt-2"
|
||||
size="small"
|
||||
:segmented="{ content: true }"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<template #header-extra>
|
||||
<n-space>
|
||||
<n-button
|
||||
type="primary"
|
||||
@click="addJoin"
|
||||
:disabled="formValue.options?.join?.length >= 20"
|
||||
>新增关联表</n-button
|
||||
>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<n-form ref="formRef" :model="formValue">
|
||||
<n-alert type="warning" :show-icon="false" v-if="formValue.options?.join?.length > 3">关联表数量建议在三个以下</n-alert>
|
||||
<div class="mt-4"></div>
|
||||
<n-row :gutter="6" v-for="(join, index) in formValue.options.join" :key="index">
|
||||
<n-col :span="6" style="min-width: 200px">
|
||||
<n-form-item label="关联表" path="join.linkTable">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="tablesLoading"
|
||||
placeholder="请选择"
|
||||
:options="linkTablesOption"
|
||||
v-model:value="join.linkTable"
|
||||
@update:value="handleLinkTableUpdateValue(join)"
|
||||
:disabled="formValue.dbName === ''"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="3" style="min-width: 100px">
|
||||
<n-form-item
|
||||
label="别名"
|
||||
path="join.alias"
|
||||
v-show="formValue.genType >= 10 && formValue.genType < 20"
|
||||
>
|
||||
<n-input
|
||||
placeholder="请输入"
|
||||
v-model:value="join.alias"
|
||||
@update:value="updateJoinAlias"
|
||||
/>
|
||||
|
||||
<template #feedback> {{ joinAliasFeedback }}</template>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="3" style="min-width: 100px">
|
||||
<n-form-item label="关联方式" path="join.linkMode">
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.linkMode"
|
||||
v-model:value="join.linkMode"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
<n-col :span="5" style="min-width: 180px">
|
||||
<n-form-item label="关联字段" path="join.field">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="linkColumnsLoading"
|
||||
placeholder="请选择"
|
||||
:options="linkColumnsOption[join.uuid]"
|
||||
v-model:value="join.field"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="5" style="min-width: 180px">
|
||||
<n-form-item label="主表关联字段" path="join.masterField">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
:loading="columnsLoading"
|
||||
placeholder="请选择"
|
||||
:options="columnsOption"
|
||||
v-model:value="join.masterField"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-col>
|
||||
|
||||
<n-col :span="2" style="min-width: 50px">
|
||||
<n-space>
|
||||
<n-form-item label="操作" path="title">
|
||||
<n-button @click="delJoin(join, index)" size="small" strong secondary type="error"
|
||||
>移除</n-button
|
||||
>
|
||||
</n-form-item>
|
||||
</n-space>
|
||||
</n-col>
|
||||
</n-row>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</n-spin>
|
||||
|
||||
<MenuModal ref="menuModalRef" @reloadTable="loadMenuTreeOption" />
|
||||
<SetFuncDict
|
||||
ref="setFuncDictRef"
|
||||
@update="handleUpdateFuncDict"
|
||||
:columnsOption="columnsOption"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, computed, watch } from 'vue';
|
||||
import { FormInst, useDialog, useMessage } from 'naive-ui';
|
||||
import { newState, selectListObj } from './model';
|
||||
import { TableSelect, ColumnSelect } from '@/api/develop/code';
|
||||
import { getRandomString } from '@/utils/charset';
|
||||
import IconSelector from '@/components/IconSelector/index.vue';
|
||||
import { QuestionCircleOutlined } from '@vicons/antd';
|
||||
import { getMenuList } from '@/api/system/menu';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isLetterBegin } from '@/utils/is';
|
||||
import MenuModal from '@/views/permission/menu/menuModal.vue';
|
||||
import SetFuncDict from './SetFuncDict.vue';
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const timer = ref();
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const bodyShow = ref(true);
|
||||
const tablesLoading = ref(false);
|
||||
const columnsLoading = ref(false);
|
||||
const linkColumnsLoading = ref(false);
|
||||
const tablesOption = ref<any>([]); // 数据库表选项
|
||||
const columnsOption = ref<any>([]); // 主表字段选项
|
||||
const linkTablesOption = ref<any>([]); // 关联表选项
|
||||
const linkColumnsOption = ref<any>([]); // 关联表字段选项
|
||||
const menuModalRef = ref();
|
||||
const setFuncDictRef = ref();
|
||||
|
||||
const optionMenuTree = ref([
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '根目录',
|
||||
pid: 0,
|
||||
title: '根目录',
|
||||
type: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
interface Props {
|
||||
value?: any;
|
||||
selectList: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: newState(null),
|
||||
selectList: selectListObj,
|
||||
});
|
||||
|
||||
watch(props, async (newVal, oldVal) => {
|
||||
if (newVal.value.dbName != oldVal.value.dbName) {
|
||||
await instLoad();
|
||||
}
|
||||
});
|
||||
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
timer.value = setInterval(async () => {
|
||||
if (props.value.id > 0) {
|
||||
clearTimeout(timer.value);
|
||||
await instLoad();
|
||||
// 切换tab时会导致选项被清空,这里重新进行加载
|
||||
await loadLinkColumnsOption();
|
||||
await loadMenuTreeOption();
|
||||
bodyShow.value = false;
|
||||
}
|
||||
}, 30);
|
||||
});
|
||||
|
||||
const loadMenuTreeOption = async () => {
|
||||
const options = await getMenuList();
|
||||
optionMenuTree.value = [
|
||||
{
|
||||
id: 0,
|
||||
key: 0,
|
||||
label: '根目录',
|
||||
pid: 0,
|
||||
title: '根目录',
|
||||
type: 1,
|
||||
},
|
||||
];
|
||||
optionMenuTree.value = optionMenuTree.value.concat(options.list);
|
||||
};
|
||||
|
||||
const loadSelect = async () => {
|
||||
columnsOption.value = await loadColumnSelect(formValue.value.tableName);
|
||||
};
|
||||
|
||||
async function instLoad() {
|
||||
columnsLoading.value = true;
|
||||
tablesLoading.value = true;
|
||||
await loadSelect();
|
||||
await loadTableSelect(formValue.value.dbName);
|
||||
tablesLoading.value = false;
|
||||
columnsLoading.value = false;
|
||||
}
|
||||
|
||||
async function loadLinkColumnsOption() {
|
||||
if (formValue.value.options.join === undefined) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < formValue.value.options.join.length; i++) {
|
||||
linkColumnsLoading.value = true;
|
||||
linkColumnsOption.value[formValue.value.options.join[i].uuid] = await loadColumnSelect(
|
||||
formValue.value.options.join[i].linkTable
|
||||
);
|
||||
linkColumnsLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理选项更新
|
||||
async function handleDbUpdateValue(value, _option) {
|
||||
tablesLoading.value = true;
|
||||
await loadTableSelect(value);
|
||||
tablesLoading.value = false;
|
||||
}
|
||||
|
||||
async function loadTableSelect(value) {
|
||||
const options = await TableSelect({ name: value });
|
||||
tablesOption.value = cloneDeep(options);
|
||||
linkTablesOption.value = cloneDeep(options);
|
||||
}
|
||||
|
||||
async function loadColumnSelect(value) {
|
||||
return await ColumnSelect({ name: formValue.value.dbName, table: value });
|
||||
}
|
||||
|
||||
function handleTableUpdateValue(value, option) {
|
||||
formValue.value.varName = option?.defVarName as string;
|
||||
formValue.value.daoName = option?.daoName as string;
|
||||
formValue.value.tableComment = option?.defTableComment as string;
|
||||
}
|
||||
|
||||
function addJoin() {
|
||||
if (formValue.value.options.join === undefined) {
|
||||
formValue.value.options.join = [];
|
||||
}
|
||||
let uuid = getRandomString(16, true);
|
||||
formValue.value.options.join.push({
|
||||
uuid: uuid,
|
||||
linkTable: '',
|
||||
alias: '',
|
||||
linkMode: 1,
|
||||
field: '',
|
||||
masterField: '',
|
||||
daoName: '',
|
||||
columns: [],
|
||||
});
|
||||
linkColumnsOption.value[uuid] = [];
|
||||
}
|
||||
|
||||
function delJoin(join, index) {
|
||||
formValue.value.options.join.splice(index, 1);
|
||||
delete linkColumnsOption.value[join.uuid];
|
||||
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
|
||||
if (i > -1) {
|
||||
linkTablesOption.value[i].disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleLinkTableUpdateValue(join) {
|
||||
let i = linkTablesOption.value.findIndex((res) => res.value === join.linkTable);
|
||||
if (i > -1) {
|
||||
join.alias = linkTablesOption.value[i].defAlias;
|
||||
join.daoName = linkTablesOption.value[i].daoName;
|
||||
linkTablesOption.value[i].disabled = true;
|
||||
}
|
||||
|
||||
linkColumnsLoading.value = true;
|
||||
linkColumnsOption.value[join.uuid] = await loadColumnSelect(join.linkTable);
|
||||
// 清空更新前的字段
|
||||
join.field = '';
|
||||
linkColumnsLoading.value = false;
|
||||
}
|
||||
|
||||
const joinAliasFeedback = ref('');
|
||||
function updateJoinAlias(value: string) {
|
||||
if (value.length < 3) {
|
||||
joinAliasFeedback.value = '别名不能小于3位';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLetterBegin(value)) {
|
||||
joinAliasFeedback.value = '别名必须以字母开头';
|
||||
return;
|
||||
}
|
||||
joinAliasFeedback.value = '';
|
||||
}
|
||||
|
||||
function handleUpdateMenuPid(value: string | number | Array<string | number> | null) {
|
||||
formValue.value.options.menu.pid = value;
|
||||
}
|
||||
|
||||
function handleAddMenu() {
|
||||
menuModalRef.value.openModal();
|
||||
}
|
||||
|
||||
function handleCheckboxGenFuncDict() {
|
||||
if (formValue.value.options.autoOps.includes('genFuncDict')) {
|
||||
setFuncDictRef.value.openModal(formValue.value.options.funcDict);
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdateFuncDict(value) {
|
||||
formValue.value.options.funcDict = value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.default_text_value) {
|
||||
color: var(--n-tab-text-color-active);
|
||||
}
|
||||
::v-deep(.tips-help-icon) {
|
||||
margin-left: -16px;
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,651 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<n-spin :show="show" description="加载中...">
|
||||
<BasicTable
|
||||
size="small"
|
||||
striped
|
||||
resizable
|
||||
canResize
|
||||
virtual-scroll
|
||||
:single-line="false"
|
||||
:showTopRight="false"
|
||||
:pagination="false"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:row-key="(row) => row.id"
|
||||
:resizeHeightOffset="-20000"
|
||||
:scroll-x="columnCollapse ? 1400 : 2400"
|
||||
:scroll-y="720"
|
||||
:scrollbar-props="{ trigger: 'none' }"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-space>
|
||||
<n-popconfirm @positive-click="reloadColumns">
|
||||
<template #trigger>
|
||||
<n-button type="primary" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Reload />
|
||||
</n-icon>
|
||||
</template>
|
||||
重置字段
|
||||
</n-button>
|
||||
</template>
|
||||
重置后将从数据库重新加载表,不保留当前字段配置,确定要重置吗?
|
||||
</n-popconfirm>
|
||||
|
||||
<n-popconfirm @positive-click="syncColumns">
|
||||
<template #trigger>
|
||||
<n-button type="primary" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Sync />
|
||||
</n-icon>
|
||||
</template>
|
||||
同步字段
|
||||
</n-button>
|
||||
</template>
|
||||
同步是从数据库重新加载表,保留当前有效的字段配置,确定要同步吗?
|
||||
</n-popconfirm>
|
||||
|
||||
<n-button type="default" class="min-left-space" @click="handleMove">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<MoveOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
移动字段
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-spin>
|
||||
</n-card>
|
||||
<Move ref="moveRef" v-model:columns="dataSource" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, ref } from 'vue';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import {
|
||||
formatColumns,
|
||||
formGridColsOptions,
|
||||
formGridSpanOptions,
|
||||
genInfoObj,
|
||||
selectListObj,
|
||||
} from '@/views/develop/code/components/model';
|
||||
import { ColumnList } from '@/api/develop/code';
|
||||
import { NInputNumber, NSpace, NButton, NCheckbox, NInput, NSelect, NCascader } from 'naive-ui';
|
||||
import { HelpCircleOutline, Reload, Sync, MoveOutline } from '@vicons/ionicons5';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { renderIcon, renderTooltip } from '@/utils';
|
||||
import Move from './Move.vue';
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
interface Props {
|
||||
value?: any;
|
||||
selectList: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: genInfoObj,
|
||||
selectList: selectListObj,
|
||||
});
|
||||
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
const show = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const moveRef = ref();
|
||||
const columnCollapse = ref(true);
|
||||
const columnsCollapseData = computed(() => {
|
||||
return columnCollapse.value
|
||||
? [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
key: 'dc',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
onUpdateValue: function (e) {
|
||||
row.dc = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '物理类型',
|
||||
key: 'sqlType',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'Go属性',
|
||||
key: 'goName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Go类型',
|
||||
key: 'goType',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'Ts属性',
|
||||
key: 'tsName',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Ts类型',
|
||||
key: 'tsType',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
key: 'dc',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
onUpdateValue: function (e) {
|
||||
row.dc = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const columns = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
key: 'id',
|
||||
width: 30,
|
||||
render(row, index) {
|
||||
return index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return h('div', null, [
|
||||
renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
type: 'primary',
|
||||
style: { 'margin-left': '20px' },
|
||||
onClick: () => (columnCollapse.value = !columnCollapse.value),
|
||||
},
|
||||
{ default: () => (columnCollapse.value ? '展开 >>' : '折叠 <<') }
|
||||
),
|
||||
]);
|
||||
},
|
||||
key: 'field',
|
||||
align: 'center',
|
||||
width: 800,
|
||||
children: columnsCollapseData.value,
|
||||
},
|
||||
{
|
||||
width: 800,
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '新增/编辑表单', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'勾选编辑以后会在新增、编辑表单中显示该字段;当同时勾选列表查询时,会优先使用配置的表单组件'
|
||||
);
|
||||
},
|
||||
key: 'edit',
|
||||
align: 'center',
|
||||
children: [
|
||||
{
|
||||
align: 'center',
|
||||
title: '编辑',
|
||||
key: 'isEdit',
|
||||
width: 30,
|
||||
render(row) {
|
||||
const disabled = isEditDisabled(row);
|
||||
const checkbox = h(NCheckbox, {
|
||||
defaultChecked: row.isEdit,
|
||||
disabled: disabled,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isEdit = e;
|
||||
},
|
||||
});
|
||||
if (!disabled) {
|
||||
return checkbox;
|
||||
}
|
||||
return renderTooltip(checkbox, '该字段属性由系统维护,无需单独配置!');
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '必填',
|
||||
key: 'required',
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.required,
|
||||
disabled: row.name === 'id',
|
||||
onUpdateChecked: function (e) {
|
||||
row.required = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '唯一',
|
||||
key: 'unique',
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.unique,
|
||||
disabled: row.name === 'id',
|
||||
onUpdateChecked: function (e) {
|
||||
row.unique = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '表单组件',
|
||||
key: 'formMode',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.formMode,
|
||||
options: getFormModeOptions(row.tsType),
|
||||
onUpdateValue: function (e) {
|
||||
row.formMode = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '绑定字典',
|
||||
key: 'dictType',
|
||||
width: 100,
|
||||
render(row) {
|
||||
if (row.dictType == 0) {
|
||||
row.dictType = null;
|
||||
}
|
||||
return h(NCascader, {
|
||||
placeholder: ' ',
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
showPath: false,
|
||||
checkStrategy: 'child',
|
||||
disabled: row.name === 'id',
|
||||
value: row.dictType,
|
||||
options: props.selectList?.dictMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.dictType = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '验证规则',
|
||||
key: 'formRole',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.formRole,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.formRole ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.formRole = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return h(NSpace, { inline: true }, [
|
||||
renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '栅格', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'表单每行摆放组件的个数。响应式栅格,小屏幕自动转为每行摆放一个组件。参考文档:https://www.naiveui.com/zh-CN/os-theme/components/grid#responsive-item.vue'
|
||||
),
|
||||
h(NSelect, {
|
||||
style: { width: '100px' },
|
||||
size: 'small',
|
||||
consistentMenuWidth: false,
|
||||
value: formValue.value.options.presetStep.formGridCols,
|
||||
options: formGridColsOptions,
|
||||
onUpdateValue: function (e) {
|
||||
formValue.value.options.presetStep.formGridCols = e;
|
||||
},
|
||||
}),
|
||||
]);
|
||||
},
|
||||
key: 'formGridSpan',
|
||||
width: 120,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
disabled: row.name === 'id',
|
||||
value: row.formGridSpan,
|
||||
options: getFormGridSpanOptions(formValue.value.options.presetStep.formGridCols),
|
||||
onUpdateValue: function (e) {
|
||||
row.formGridSpan = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
width: 800,
|
||||
title: '列表',
|
||||
key: 'list',
|
||||
align: 'center',
|
||||
children: [
|
||||
{
|
||||
title: '列表',
|
||||
key: 'isList',
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isList,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isList = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '导出',
|
||||
key: 'isExport',
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isExport,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isExport = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查询',
|
||||
key: 'isQuery',
|
||||
width: 30,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isQuery,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isQuery = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查询条件',
|
||||
key: 'queryWhere',
|
||||
width: 90,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.queryWhere,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.whereMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.queryWhere = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排列方式',
|
||||
key: 'align',
|
||||
width: 80,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.align,
|
||||
options: props.selectList?.tableAlign ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.align = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '列宽', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'选填。设定固定值时表格生成自动计算scroll-x,未设定默认每列按100计算'
|
||||
);
|
||||
},
|
||||
key: 'width',
|
||||
width: 50,
|
||||
render(row) {
|
||||
return h(NInputNumber, {
|
||||
value: row.width,
|
||||
placeholder: ' ',
|
||||
min: -1,
|
||||
max: 2000,
|
||||
showButton: false,
|
||||
onUpdateValue: function (e) {
|
||||
row.width = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
// 同步字段
|
||||
function syncColumns() {
|
||||
show.value = true;
|
||||
dataSource.value = [];
|
||||
|
||||
const params = {
|
||||
name: formValue.value.dbName,
|
||||
table: formValue.value.tableName,
|
||||
};
|
||||
|
||||
ColumnList(params)
|
||||
.then((res) => {
|
||||
const columns = formatColumns(res);
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
// 相同字段名称和类型,保留原字段属性
|
||||
const index = formValue.value.masterColumns.findIndex(
|
||||
(item) => item.name == columns[i].name && item.dataType == columns[i].dataType
|
||||
);
|
||||
if (index !== -1) {
|
||||
columns[i] = formValue.value.masterColumns[index];
|
||||
}
|
||||
}
|
||||
|
||||
formValue.value.masterColumns = columns;
|
||||
dataSource.value = formValue.value.masterColumns;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 重载字段属性
|
||||
function reloadColumns() {
|
||||
show.value = true;
|
||||
dataSource.value = [];
|
||||
|
||||
const params = {
|
||||
name: formValue.value.dbName,
|
||||
table: formValue.value.tableName,
|
||||
};
|
||||
|
||||
ColumnList(params)
|
||||
.then((res) => {
|
||||
formValue.value.masterColumns = formatColumns(res);
|
||||
dataSource.value = formValue.value.masterColumns;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function getFormModeOptions(type: string) {
|
||||
const options = cloneDeep(props.selectList?.formMode ?? []);
|
||||
if (options.length === 0) {
|
||||
return [];
|
||||
}
|
||||
switch (type) {
|
||||
case 'number':
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const allows = [
|
||||
'Input',
|
||||
'InputNumber',
|
||||
'Radio',
|
||||
'Select',
|
||||
'Switch',
|
||||
'Rate',
|
||||
'TreeSelect',
|
||||
'Cascader',
|
||||
];
|
||||
if (!allows.includes(options[i].value)) {
|
||||
options[i].disabled = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
options.sort((a, b) => (a.disabled === b.disabled ? 0 : a.disabled ? 1 : -1));
|
||||
return options;
|
||||
}
|
||||
|
||||
function getFormGridSpanOptions(cols: number) {
|
||||
if (cols < 1) {
|
||||
cols = 1;
|
||||
}
|
||||
if (cols > 4) {
|
||||
cols = 4;
|
||||
}
|
||||
for (let i = 0; i < formValue.value.masterColumns.length; i++) {
|
||||
if (!formValue.value.masterColumns[i].formGridSpan) {
|
||||
formValue.value.masterColumns[i].formGridSpan = 1;
|
||||
}
|
||||
if (formValue.value.masterColumns[i].formGridSpan > cols) {
|
||||
formValue.value.masterColumns[i].formGridSpan = cols;
|
||||
}
|
||||
}
|
||||
return formGridSpanOptions.slice(0, Math.min(cols, formGridSpanOptions.length));
|
||||
}
|
||||
|
||||
// 禁止编辑的字段,由系统维护
|
||||
function isEditDisabled(row) {
|
||||
const disabledNames = [
|
||||
'id',
|
||||
'created_by',
|
||||
'updated_by',
|
||||
'deleted_by',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'deleted_at',
|
||||
];
|
||||
if (disabledNames.includes(row.name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (formValue.value.genType == 11) {
|
||||
const disabledTreeNames = ['pid', 'level', 'tree'];
|
||||
if (disabledTreeNames.includes(row.name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleMove() {
|
||||
moveRef.value.openModal();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (formValue.value.masterColumns.length === 0) {
|
||||
reloadColumns();
|
||||
} else {
|
||||
show.value = true;
|
||||
setTimeout(function () {
|
||||
dataSource.value = formValue.value.masterColumns;
|
||||
show.value = false;
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tree-tips {
|
||||
margin-left: 12px;
|
||||
color: #18a058;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,430 @@
|
||||
<template>
|
||||
<n-spin :show="show" description="加载中...">
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
size="small"
|
||||
striped
|
||||
resizable
|
||||
canResize
|
||||
:single-line="false"
|
||||
:showTopRight="false"
|
||||
:pagination="false"
|
||||
:columns="columns"
|
||||
:dataSource="dataSource"
|
||||
:row-key="(row) => row.id"
|
||||
:resizeHeightOffset="-20000"
|
||||
:scroll-x="columnCollapse ? 880 : 1880"
|
||||
:scroll-y="720"
|
||||
:scrollbar-props="{ trigger: 'none' }"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-space>
|
||||
<n-popconfirm @positive-click="reloadColumns(getIndex())">
|
||||
<template #trigger>
|
||||
<n-button type="primary" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Reload />
|
||||
</n-icon>
|
||||
</template>
|
||||
重置字段
|
||||
</n-button>
|
||||
</template>
|
||||
重置后将从数据库重新加载该表的默认配置,确定要重置吗?
|
||||
</n-popconfirm>
|
||||
|
||||
<n-popconfirm @positive-click="syncColumns(getIndex())">
|
||||
<template #trigger>
|
||||
<n-button type="primary" class="min-left-space">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<Sync />
|
||||
</n-icon>
|
||||
</template>
|
||||
同步字段
|
||||
</n-button>
|
||||
</template>
|
||||
同步是从数据库重新加载表,保留当前有效的字段配置,确定要同步吗?
|
||||
</n-popconfirm>
|
||||
|
||||
<n-button type="default" class="min-left-space" @click="handleMove">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<MoveOutline />
|
||||
</n-icon>
|
||||
</template>
|
||||
移动字段
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
<Move ref="moveRef" v-model:columns="dataSource" />
|
||||
</n-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, ref } from 'vue';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { formatColumns, genInfoObj, selectListObj } from '@/views/develop/code/components/model';
|
||||
import { ColumnList } from '@/api/develop/code';
|
||||
import {
|
||||
NButton,
|
||||
NCheckbox,
|
||||
NIcon,
|
||||
NInput,
|
||||
NInputNumber,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NTooltip,
|
||||
} from 'naive-ui';
|
||||
import { HelpCircleOutline, MoveOutline, Reload, Sync, WarningOutline } from '@vicons/ionicons5';
|
||||
import { renderIcon, renderTooltip } from '@/utils';
|
||||
import Move from './Move.vue';
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
|
||||
interface Props {
|
||||
value?: any;
|
||||
selectList: any;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
value: genInfoObj,
|
||||
selectList: selectListObj,
|
||||
uuid: '',
|
||||
});
|
||||
|
||||
const formValue = computed({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(value) {
|
||||
emit('update:value', value);
|
||||
},
|
||||
});
|
||||
|
||||
const show = ref(false);
|
||||
const dataSource = ref([]);
|
||||
const moveRef = ref();
|
||||
const columnCollapse = ref(true);
|
||||
const columnsCollapseData = computed(() => {
|
||||
return columnCollapse.value
|
||||
? [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
key: 'dc',
|
||||
width: 150,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
onUpdateValue: function (e) {
|
||||
row.dc = e;
|
||||
// await saveProductCustom(row.id, 'frontShow', e);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: '字段列名',
|
||||
key: 'name',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '物理类型',
|
||||
key: 'sqlType',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: 'Go属性',
|
||||
key: 'goName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'Go类型',
|
||||
key: 'goType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'Ts属性',
|
||||
key: 'tsName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'Ts类型',
|
||||
key: 'tsType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '字段描述',
|
||||
key: 'dc',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NInput, {
|
||||
value: row.dc,
|
||||
onUpdateValue: function (e) {
|
||||
row.dc = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const columns = computed(() => {
|
||||
return [
|
||||
{
|
||||
title: '',
|
||||
key: 'id',
|
||||
width: 50,
|
||||
render(row, index) {
|
||||
return index + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return h('div', null, [
|
||||
renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '字段', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'Go类型和属性定义取决于你在/hack/config.yaml中的配置参数'
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
type: 'primary',
|
||||
style: { 'margin-left': '20px' },
|
||||
onClick: () => (columnCollapse.value = !columnCollapse.value),
|
||||
},
|
||||
{ default: () => (columnCollapse.value ? '展开 >>' : '折叠 <<') }
|
||||
),
|
||||
]);
|
||||
},
|
||||
key: 'field',
|
||||
align: 'center',
|
||||
width: 800,
|
||||
children: columnsCollapseData.value,
|
||||
},
|
||||
{
|
||||
width: 800,
|
||||
title: '列表',
|
||||
key: 'list',
|
||||
align: 'center',
|
||||
children: [
|
||||
{
|
||||
title: '列表',
|
||||
key: 'isList',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isList,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isList = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '导出',
|
||||
key: 'isExport',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isExport,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isExport = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查询',
|
||||
key: 'isQuery',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render(row) {
|
||||
return h(NCheckbox, {
|
||||
defaultChecked: row.isQuery,
|
||||
onUpdateChecked: function (e) {
|
||||
row.isQuery = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '查询条件',
|
||||
key: 'queryWhere',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.queryWhere,
|
||||
disabled: row.name === 'id',
|
||||
options: props.selectList?.whereMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.queryWhere = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '排列方式',
|
||||
key: 'align',
|
||||
width: 100,
|
||||
render(row) {
|
||||
return h(NSelect, {
|
||||
consistentMenuWidth: false,
|
||||
value: row.align,
|
||||
options: props.selectList?.tableAlign ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.align = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title(_column) {
|
||||
return renderTooltip(
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
strong: true,
|
||||
size: 'small',
|
||||
text: true,
|
||||
iconPlacement: 'right',
|
||||
},
|
||||
{ default: () => '列宽', icon: renderIcon(HelpCircleOutline) }
|
||||
),
|
||||
'选填。设定固定值时表格生成自动计算scroll-x,未设定默认每列按100计算'
|
||||
);
|
||||
},
|
||||
key: 'width',
|
||||
width: 50,
|
||||
render(row) {
|
||||
return h(NInputNumber, {
|
||||
value: row.width,
|
||||
placeholder: ' ',
|
||||
min: 0,
|
||||
max: 2000,
|
||||
showButton: false,
|
||||
onUpdateValue: function (e) {
|
||||
row.width = e;
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleMove() {
|
||||
moveRef.value.openModal();
|
||||
}
|
||||
function getIndex() {
|
||||
if (formValue.value.options.join.length === 0) {
|
||||
return -1;
|
||||
}
|
||||
for (let i = 0; i < formValue.value.options.join.length; i++) {
|
||||
if (formValue.value.options.join[i].uuid === props.uuid) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 同步字段属性
|
||||
function syncColumns(index: number) {
|
||||
show.value = true;
|
||||
dataSource.value = [];
|
||||
|
||||
const join = formValue.value.options.join[index];
|
||||
const params = {
|
||||
name: formValue.value.dbName,
|
||||
table: join.linkTable,
|
||||
isLink: 1,
|
||||
alias: join.alias,
|
||||
};
|
||||
|
||||
ColumnList(params)
|
||||
.then((res) => {
|
||||
const columns = formatColumns(res);
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
// 相同字段名称和类型,保留原字段属性
|
||||
const index2 = join.columns.findIndex(
|
||||
(item) => item.name == columns[i].name && item.dataType == columns[i].dataType
|
||||
);
|
||||
if (index2 !== -1) {
|
||||
columns[i] = join.columns[index2];
|
||||
}
|
||||
}
|
||||
join.columns = columns;
|
||||
dataSource.value = join.columns;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 重载字段属性
|
||||
function reloadColumns(index: number) {
|
||||
show.value = true;
|
||||
dataSource.value = [];
|
||||
|
||||
const join = formValue.value.options.join[index];
|
||||
const params = {
|
||||
name: formValue.value.dbName,
|
||||
table: join.linkTable,
|
||||
isLink: 1,
|
||||
alias: join.alias,
|
||||
};
|
||||
|
||||
ColumnList(params)
|
||||
.then((res) => {
|
||||
join.columns = formatColumns(res);
|
||||
dataSource.value = join.columns;
|
||||
})
|
||||
.finally(() => {
|
||||
show.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
show.value = true;
|
||||
setTimeout(() => {
|
||||
const index = getIndex();
|
||||
|
||||
// 已存在直接加载
|
||||
if (formValue.value.options.join[index].columns.length > 0) {
|
||||
dataSource.value = formValue.value.options.join[index].columns;
|
||||
show.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
reloadColumns(index);
|
||||
}, 100);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
transform-origin="center"
|
||||
title="移动字段"
|
||||
:style="{
|
||||
width: dialogWidth,
|
||||
}"
|
||||
>
|
||||
<n-scrollbar style="max-height: 87vh" class="pr-5">
|
||||
<n-card
|
||||
:bordered="false"
|
||||
:content-style="{ padding: '0px' }"
|
||||
:header-style="{ padding: 'px' }"
|
||||
:segmented="true"
|
||||
>
|
||||
请通过拖拽来移动字段的位置。
|
||||
<div class="mt-8"></div>
|
||||
<Draggable
|
||||
class="draggable-ul"
|
||||
animation="300"
|
||||
:list="columns"
|
||||
group="people"
|
||||
itemKey="name"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<div class="cursor-move draggable-li">
|
||||
<n-tag type="default" size="small" style="font-weight: 800">{{
|
||||
element.name
|
||||
}}</n-tag
|
||||
><span class="ml-2">{{ element.dc }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
</n-card>
|
||||
</n-scrollbar>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
const showModal = ref(false);
|
||||
const columns = defineModel<[]>('columns');
|
||||
const dialogWidth = computed(() => {
|
||||
return adaModalWidth(360);
|
||||
});
|
||||
|
||||
function openModal() {
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.draggable-ul {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin-top: -8px;
|
||||
|
||||
.draggable-li {
|
||||
width: 100%;
|
||||
padding: 8px 4px;
|
||||
color: #333;
|
||||
border-bottom: 1px solid #efeff5;
|
||||
}
|
||||
|
||||
.draggable-li:hover {
|
||||
background-color: rgba(229, 231, 235, var(--tw-border-opacity));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<div>
|
||||
<textarea id="copy-code" :value="content"></textarea>
|
||||
<n-tabs type="line" animated>
|
||||
<n-tab-pane v-for="(view, index) in views" :key="index" :name="view.name" :tab="view.name">
|
||||
<n-space justify="space-between">
|
||||
<n-tag :type="view.tag.type" class="tag-margin">
|
||||
{{ view.tag.label }}
|
||||
<template #icon>
|
||||
<n-icon :component="view.tag.icon" />
|
||||
</template>
|
||||
{{ view.path }}
|
||||
</n-tag>
|
||||
<n-button type="primary" size="small" class="tag-margin" @click="handleCopy(view.content)"
|
||||
>复制本页代码</n-button
|
||||
>
|
||||
</n-space>
|
||||
<n-scrollbar class="code-scrollbar" trigger="none">
|
||||
<n-code
|
||||
:class="'code-' + getFileExtension(view.path)"
|
||||
:code="view.content"
|
||||
:hljs="hljs"
|
||||
:language="getFileExtension(view.path)"
|
||||
show-line-numbers
|
||||
/>
|
||||
</n-scrollbar>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import hljs from 'highlight.js/lib/core';
|
||||
import go from 'highlight.js/lib/languages/go';
|
||||
import typescript from 'highlight.js/lib/languages/typescript';
|
||||
import xml from 'highlight.js/lib/languages/xml';
|
||||
import sql from 'highlight.js/lib/languages/sql';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import {
|
||||
CheckmarkCircle,
|
||||
CheckmarkDoneCircle,
|
||||
CloseCircleOutline,
|
||||
HelpCircleOutline,
|
||||
RemoveCircleOutline,
|
||||
} from '@vicons/ionicons5';
|
||||
import { useMessage } from 'naive-ui';
|
||||
|
||||
hljs.registerLanguage('go', go);
|
||||
hljs.registerLanguage('ts', typescript);
|
||||
hljs.registerLanguage('sql', sql);
|
||||
hljs.registerLanguage('vue', xml);
|
||||
|
||||
interface Props {
|
||||
previewModel: any;
|
||||
showModal: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
previewModel: cloneDeep({ views: {} }),
|
||||
showModal: false,
|
||||
});
|
||||
const message = useMessage();
|
||||
const content = ref('');
|
||||
const views = computed(() => {
|
||||
let tmpViews: any = [];
|
||||
let i = 0;
|
||||
for (const [k, v] of Object.entries(props.previewModel.views)) {
|
||||
let item = v as any;
|
||||
item.name = k;
|
||||
switch (item.meth) {
|
||||
case 1:
|
||||
item.tag = { type: 'success', label: '创建文件', icon: CheckmarkCircle };
|
||||
break;
|
||||
case 2:
|
||||
item.tag = { type: 'warning', label: '覆盖文件', icon: CheckmarkDoneCircle };
|
||||
break;
|
||||
case 3:
|
||||
item.tag = { type: 'info', label: '已存在跳过', icon: CloseCircleOutline };
|
||||
break;
|
||||
case 4:
|
||||
item.tag = { type: 'error', label: '不生成', icon: RemoveCircleOutline };
|
||||
break;
|
||||
default:
|
||||
item.tag = { type: 'error', label: '未知状态', icon: HelpCircleOutline };
|
||||
}
|
||||
tmpViews[i] = item;
|
||||
i++;
|
||||
}
|
||||
return tmpViews;
|
||||
});
|
||||
|
||||
function getFileExtension(path: string): string {
|
||||
const parts = path.split('.');
|
||||
if (parts.length > 1) {
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function handleCopy(code: string) {
|
||||
content.value = code;
|
||||
setTimeout(function () {
|
||||
const copyVal = document.getElementById('copy-code');
|
||||
copyVal.select();
|
||||
document.execCommand('copy');
|
||||
message.success('已复制');
|
||||
}, 20);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
::v-deep(.alert-margin) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
::v-deep(.tag-margin) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
::v-deep(.code-scrollbar) {
|
||||
height: calc(100vh - 300px);
|
||||
background: #282b2e;
|
||||
color: #e0e2e4;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
::v-deep(.code-vue .hljs-tag) {
|
||||
color: rgb(242, 197, 92);
|
||||
}
|
||||
|
||||
::v-deep(.code-vue .hljs-name) {
|
||||
color: rgb(242, 197, 92);
|
||||
}
|
||||
|
||||
::v-deep(.code-vue .hljs-attr) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
::v-deep(.code-go .hljs-params) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
::v-deep(.code-ts .hljs-params) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
::v-deep(.code-ts .hljs-property) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
::v-deep(.code-ts .hljs-function) {
|
||||
color: rgb(49, 104, 213);
|
||||
}
|
||||
|
||||
#copy-code {
|
||||
position: fixed;
|
||||
top: -100px;
|
||||
left: -100px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
title="设置选项字段"
|
||||
v-model:show="showModal"
|
||||
:block-scroll="false"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
>
|
||||
<n-form
|
||||
:model="formValue"
|
||||
:rules="rules"
|
||||
ref="formRef"
|
||||
label-placement="left"
|
||||
:label-width="100"
|
||||
class="py-4"
|
||||
>
|
||||
<n-form-item label="选项值" path="valueColumn">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
placeholder="请选择"
|
||||
:options="columnsOption"
|
||||
v-model:value="formValue.valueColumn"
|
||||
/>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="选项名称" path="labelColumn">
|
||||
<n-select
|
||||
filterable
|
||||
tag
|
||||
placeholder="请选择"
|
||||
:options="columnsOption"
|
||||
v-model:value="formValue.labelColumn"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="closeForm">取消</n-button>
|
||||
<n-button type="info" @click="confirmForm">保存</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { rules } from '@/views/addons/stock/itemBrand/model';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { Edit } from '@/api/addons/stock/itemClass';
|
||||
|
||||
interface Props {
|
||||
columnsOption: any;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
columnsOption: [],
|
||||
});
|
||||
|
||||
const rules = {
|
||||
valueColumn: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '选项值不能为空',
|
||||
},
|
||||
labelColumn: {
|
||||
required: true,
|
||||
trigger: ['blur', 'input'],
|
||||
type: 'string',
|
||||
message: '选项名称不能为空',
|
||||
},
|
||||
};
|
||||
|
||||
const message = useMessage();
|
||||
const emit = defineEmits(['update']);
|
||||
const showModal = ref(false);
|
||||
const formRef = ref();
|
||||
const formValue = ref({
|
||||
valueColumn: null,
|
||||
labelColumn: null,
|
||||
});
|
||||
|
||||
function openModal(state) {
|
||||
showModal.value = true;
|
||||
if (!state) {
|
||||
state = {
|
||||
valueColumn: null,
|
||||
labelColumn: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (!state.valueColumn) {
|
||||
const item = props.columnsOption.find((item) => item.value === 'id');
|
||||
if (item) {
|
||||
state.valueColumn = item.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.labelColumn) {
|
||||
const item = props.columnsOption.find((item) => item.value === 'title' || item.value === 'name');
|
||||
if (item) {
|
||||
state.labelColumn = item.value;
|
||||
}
|
||||
}
|
||||
formValue.value = cloneDeep(state);
|
||||
emit('update', formValue.value);
|
||||
}
|
||||
|
||||
function closeForm() {
|
||||
showModal.value = false;
|
||||
}
|
||||
|
||||
function confirmForm(e) {
|
||||
e.preventDefault();
|
||||
formRef.value.validate((errors) => {
|
||||
if (!errors) {
|
||||
emit('update', formValue.value);
|
||||
closeForm();
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,142 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { isJsonString } from '@/utils/is';
|
||||
|
||||
export const genFileObj = {
|
||||
meth: 1,
|
||||
content: '',
|
||||
path: '',
|
||||
required: true,
|
||||
};
|
||||
|
||||
export interface joinAttr {
|
||||
uuid: string;
|
||||
linkTable: string;
|
||||
alias: string;
|
||||
linkMode: number;
|
||||
field: string;
|
||||
masterField: string;
|
||||
columns: any;
|
||||
}
|
||||
|
||||
export const genInfoObj = {
|
||||
id: 0,
|
||||
genType: 10,
|
||||
genTemplate: null,
|
||||
varName: '',
|
||||
options: {
|
||||
headOps: ['add', 'batchDel', 'export'],
|
||||
columnOps: ['edit', 'del', 'view', 'status', 'check'],
|
||||
autoOps: ['genMenuPermissions', 'runDao', 'runService'],
|
||||
join: [],
|
||||
menu: {
|
||||
pid: 0,
|
||||
icon: 'MenuOutlined',
|
||||
sort: 0,
|
||||
},
|
||||
tree: {
|
||||
titleColumn: null,
|
||||
styleType: 1,
|
||||
},
|
||||
funcDict: {
|
||||
valueColumn: null,
|
||||
labelColumn: null,
|
||||
},
|
||||
presetStep: {
|
||||
formGridCols: 1,
|
||||
},
|
||||
},
|
||||
dbName: '',
|
||||
tableName: '',
|
||||
tableComment: '',
|
||||
daoName: '',
|
||||
masterColumns: [],
|
||||
addonName: null,
|
||||
status: 2,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
};
|
||||
|
||||
export const selectListObj = {
|
||||
db: [],
|
||||
genType: [],
|
||||
status: [],
|
||||
tables: [],
|
||||
formMode: [],
|
||||
formRole: [],
|
||||
dictMode: [],
|
||||
whereMode: [],
|
||||
buildMeth: [],
|
||||
tableAlign: [],
|
||||
treeStyleType: [],
|
||||
};
|
||||
|
||||
export function newState(state) {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(genInfoObj);
|
||||
}
|
||||
|
||||
export const formGridColsOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '一行一列',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '一行两列',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '一行三列',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '一行四列',
|
||||
},
|
||||
];
|
||||
|
||||
export const formGridSpanOptions = [
|
||||
{
|
||||
value: 1,
|
||||
label: '占一列位置',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '占两列位置',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '占三列位置',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '占四列位置',
|
||||
},
|
||||
];
|
||||
|
||||
// 格式化列字段
|
||||
export function formatColumns(columns: any) {
|
||||
if (columns === undefined || columns.length === 0) {
|
||||
columns = [];
|
||||
}
|
||||
|
||||
if (isJsonString(columns)) {
|
||||
columns = JSON.parse(columns);
|
||||
}
|
||||
|
||||
if (columns.length > 0) {
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
if (!columns[i].formGridSpan) {
|
||||
columns[i].formGridSpan = 1;
|
||||
}
|
||||
if (!columns[i].align) {
|
||||
columns[i].align = 'left';
|
||||
}
|
||||
if (!columns[i].width || columns[i].width < 1) {
|
||||
columns[i].width = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user