mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-11-14 21:23:47 +08:00
发布v2.13.1版本,更新内容请查看:https://github.com/bufanyun/hotgo/blob/v2.0/docs/guide-zh-CN/start-update-log.md
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hotgo",
|
||||
"version": "2.12.1",
|
||||
"version": "2.13.1",
|
||||
"author": {
|
||||
"name": "MengShuai",
|
||||
"email": "133814250@qq.com",
|
||||
@@ -30,16 +30,21 @@
|
||||
"dependencies": {
|
||||
"@vicons/antd": "^0.12.0",
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vue/runtime-core": "^3.4.19",
|
||||
"@vueup/vue-quill": "^1.2.0",
|
||||
"@vueuse/core": "^10.7.1",
|
||||
"axios": "^0.21.4",
|
||||
"date-fns": "^2.28.0",
|
||||
"echarts": "^5.3.2",
|
||||
"element-resize-detector": "^1.2.4",
|
||||
"fingerprintjs2": "^2.1.4",
|
||||
"highlight.js": "^11.8.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"naive-ui": "^2.36.0",
|
||||
"mint-filter": "^4.0.3",
|
||||
"pinia": "^2.1.7",
|
||||
"pinyin-pro": "^3.16.3",
|
||||
"print-js": "^1.6.0",
|
||||
"qrcode.vue": "3.3.3",
|
||||
"qs": "^6.10.3",
|
||||
"quill-image-uploader": "^1.3.0",
|
||||
@@ -50,6 +55,8 @@
|
||||
"vue-types": "^5.1.1",
|
||||
"vue3-json-viewer": "^2.2.2",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vue-waterfall-plugin-next": "^2.2.3",
|
||||
"spark-md5": "^3.0.2",
|
||||
"weixin-js-sdk": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
13
web/src/api/addons/hgexample/comp/index.ts
Normal file
13
web/src/api/addons/hgexample/comp/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { http } from '@/utils/http/axios';
|
||||
import type { UploadFileParams } from '@/utils/http/axios/types';
|
||||
|
||||
// 导入Excel
|
||||
export function ImportExcel(params: UploadFileParams) {
|
||||
return http.uploadFile(
|
||||
{
|
||||
url: '/hgexample/comp/importExcel',
|
||||
method: 'post',
|
||||
},
|
||||
params
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { http } from '@/utils/http/axios';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import type { UploadFileParams } from '@/utils/http/axios/types';
|
||||
|
||||
// 获取验证码
|
||||
export function GetCaptcha() {
|
||||
@@ -23,3 +24,23 @@ export function UploadImage(params) {
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
// 检查文件分片
|
||||
export function CheckMultipart(params) {
|
||||
return http.request({
|
||||
url: '/upload/checkMultipart',
|
||||
method: 'post',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 分片上传
|
||||
export function UploadPart(params: UploadFileParams) {
|
||||
return http.uploadFile(
|
||||
{
|
||||
url: '/upload/uploadPart',
|
||||
method: 'post',
|
||||
},
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,13 +23,6 @@ export function Status(params) {
|
||||
params,
|
||||
});
|
||||
}
|
||||
export function Selects(params) {
|
||||
return http.request({
|
||||
url: '/addons/selects',
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function Build(params) {
|
||||
return http.request({
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<n-modal
|
||||
v-model:show="showFileModal"
|
||||
:show-icon="false"
|
||||
:mask-closable="false"
|
||||
preset="dialog"
|
||||
:style="{
|
||||
width: width,
|
||||
@@ -41,7 +42,7 @@
|
||||
import { NModal, UploadFileInfo, useMessage } from 'naive-ui';
|
||||
import componentSetting from '@/settings/componentSetting';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import { Attachment, FileType, getFileType, UploadTag } from '@/components/FileChooser/src/model';
|
||||
import { Attachment, FileType, getFileType } from '@/components/FileChooser/src/model';
|
||||
|
||||
export interface Props {
|
||||
width?: string;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<n-button>
|
||||
<template #icon>
|
||||
<n-icon size="20">
|
||||
<component :is="formValue !== '' ? formValue : 'AntDesignOutlined'" />
|
||||
<component :is="formValue !== '' ? formValue : AntDesignOutlined" />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-button>
|
||||
@@ -38,6 +38,8 @@
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, shallowReactive } from 'vue';
|
||||
import * as AntdIcons from '@vicons/antd';
|
||||
import { AntDesignOutlined } from '@vicons/antd';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AntdSelector',
|
||||
components: AntdIcons,
|
||||
@@ -80,6 +82,7 @@
|
||||
onUpdatePage,
|
||||
onIconClick,
|
||||
formValue,
|
||||
AntDesignOutlined,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
<template v-else>
|
||||
<AntdSelector v-model:value="formValue" />
|
||||
</template>
|
||||
<n-input v-bind="$props" :value="formValue" :style="{ width: '70%' }" />
|
||||
<n-input
|
||||
v-bind="$props"
|
||||
:value="formValue"
|
||||
:style="{ width: '70%' }"
|
||||
placeholder="请选择图标"
|
||||
/>
|
||||
</n-input-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
197
web/src/components/Upload/multipartUpload.vue
Normal file
197
web/src/components/Upload/multipartUpload.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:on-after-leave="handleRemove"
|
||||
:style="{
|
||||
width: width,
|
||||
}"
|
||||
title="上传大文件"
|
||||
>
|
||||
<n-upload
|
||||
directory-dnd
|
||||
:custom-request="handleUpload"
|
||||
:on-remove="handleRemove"
|
||||
name="file"
|
||||
:disabled="uploadStatus != 0 && uploadStatus != 3"
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3">
|
||||
<FileAddOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
<template v-if="uploadStatus == 0 || uploadStatus == 3">
|
||||
<n-text style="font-size: 16px">点击或者拖动{{ typeTag }}到该区域来上传</n-text>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0">支持大文件分片上传,支持断点续传</n-p>
|
||||
</template>
|
||||
<template v-else-if="uploadStatus == 1">
|
||||
<span style="font-weight: 600">解析中,请稍候...</span>
|
||||
</template>
|
||||
<template v-else-if="uploadStatus == 2">
|
||||
<span style="font-weight: 600">正在上传({{ progress }}%)...</span>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0">文件大小:{{ sizeFormat }}</n-p>
|
||||
</template>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { NModal, UploadCustomRequestOptions, useMessage, useDialog } from 'naive-ui';
|
||||
import { FileAddOutlined } from '@vicons/antd';
|
||||
import SparkMD5 from 'spark-md5';
|
||||
import { Attachment, FileType, getFileType } from '@/components/FileChooser/src/model';
|
||||
import { CheckMultipart, UploadPart } from '@/api/base';
|
||||
import type { UploadFileParams } from '@/utils/http/axios/types';
|
||||
|
||||
export interface Props {
|
||||
width?: string;
|
||||
uploadType?: FileType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
width: '60%',
|
||||
uploadType: 'default',
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onFinish']);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const chunkSize = 2 * 1024 * 1024; // 每个分片大小限制,默认2M
|
||||
const uploadStatus = ref(0); // 上传状态 0等待上传 1解析中 2上传中 3已取消
|
||||
const progress = ref(0);
|
||||
const sizeFormat = ref('0B');
|
||||
const typeTag = computed(() => {
|
||||
return getFileType(props.uploadType);
|
||||
});
|
||||
|
||||
// 取消上传
|
||||
function handleRemove() {
|
||||
if (uploadStatus.value == 1 || uploadStatus.value == 2) {
|
||||
uploadStatus.value = 3;
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '已取消大文件上传,已上传的文件不会自动删除,重新操作可进行断点续传',
|
||||
positiveText: '确定',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
function handleUpload(options: UploadCustomRequestOptions) {
|
||||
uploadStatus.value = 1;
|
||||
|
||||
// 初始化上传进度
|
||||
updateProgress(options, 0);
|
||||
|
||||
const file = options.file.file as File;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
fileReader.onload = async (e) => {
|
||||
const spark = new SparkMD5.ArrayBuffer();
|
||||
spark.append(e.target.result);
|
||||
let md5 = spark.end();
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
let index = 0;
|
||||
let shards: any[] = [];
|
||||
while (end < file.size) {
|
||||
start = index * chunkSize;
|
||||
end = (index + 1) * chunkSize;
|
||||
|
||||
const params: UploadFileParams = {
|
||||
uploadType: props.uploadType,
|
||||
md5: md5,
|
||||
index: index + 1,
|
||||
fileName: file.name,
|
||||
file: file.slice(start, end),
|
||||
};
|
||||
|
||||
const shard = { index: index + 1, params: params };
|
||||
shards.push(shard);
|
||||
index++;
|
||||
}
|
||||
|
||||
uploadStatus.value = 2;
|
||||
|
||||
const params = {
|
||||
uploadType: props.uploadType,
|
||||
fileName: file.name,
|
||||
size: file.size,
|
||||
md5: md5,
|
||||
shardCount: shards.length,
|
||||
};
|
||||
|
||||
CheckMultipart(params)
|
||||
.then(async (res) => {
|
||||
// 已存在
|
||||
if (!res.waitUploadIndex || res.waitUploadIndex.length == 0) {
|
||||
onFinish(options, res.attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
// 断点续传,过滤掉已上传成功的分片文件
|
||||
shards = shards.filter((shard) => res.waitUploadIndex.includes(shard.index));
|
||||
if (shards.length == 0) {
|
||||
onFinish(options, res.attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
// 导入断点续传进度
|
||||
updateProgress(options, res.progress);
|
||||
sizeFormat.value = res.sizeFormat;
|
||||
|
||||
for (const item of shards) {
|
||||
if (uploadStatus.value == 3) {
|
||||
break;
|
||||
}
|
||||
item.params.uploadId = res.uploadId;
|
||||
await handleUploadPart(options, item);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
uploadStatus.value = 0;
|
||||
options.onError();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 上传分片文件
|
||||
async function handleUploadPart(options: UploadCustomRequestOptions, item) {
|
||||
const res = await UploadPart(item.params);
|
||||
updateProgress(options, res.progress);
|
||||
if (res.finish) {
|
||||
onFinish(options, res.attachment);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新上传进度
|
||||
function updateProgress(options: UploadCustomRequestOptions, value: number) {
|
||||
options.onProgress({ percent: value });
|
||||
progress.value = value;
|
||||
}
|
||||
|
||||
// 上传成功后的回调
|
||||
function onFinish(options: UploadCustomRequestOptions, result: Attachment) {
|
||||
options.onFinish();
|
||||
message.success('上传成功');
|
||||
uploadStatus.value = 0;
|
||||
emit('onFinish', result, true);
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
showModal.value = true;
|
||||
uploadStatus.value = 0;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
@@ -1,16 +1,11 @@
|
||||
export enum SocketEnum {
|
||||
EventPing = 'ping',
|
||||
EventKick = 'kick',
|
||||
EventNotice = 'notice',
|
||||
EventConnected = 'connected',
|
||||
EventAdminMonitorTrends = 'admin/monitor/trends',
|
||||
EventAdminMonitorRunInfo = 'admin/monitor/runInfo',
|
||||
EventAdminOrderNotify = 'admin/order/notify',
|
||||
TypeQueryUser = 2,
|
||||
TypeBoardCastMsg = 3,
|
||||
TypeQuerySwitcher = 4,
|
||||
TypeQueryEndlessRank = 5,
|
||||
TypeSendEmail = 6,
|
||||
TypeQueryUserGuide = 7,
|
||||
TypeRestartLog = 90,
|
||||
HeartBeatInterval = 1000,
|
||||
CodeSuc = 0,
|
||||
CodeErr = -1,
|
||||
|
||||
21
web/src/hooks/useCreateScript.ts
Normal file
21
web/src/hooks/useCreateScript.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
export default function userCreateScript(src: string) {
|
||||
const createScriptPromise = new Promise((resolve, reject) => {
|
||||
onMounted(() => {
|
||||
const script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.onload = () => {
|
||||
resolve('');
|
||||
};
|
||||
script.onerror = (error) => {
|
||||
reject(error);
|
||||
};
|
||||
script.src = src;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
});
|
||||
return {
|
||||
createScriptPromise,
|
||||
};
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import PageFooter from './index.vue';
|
||||
|
||||
export { PageFooter };
|
||||
@@ -1,56 +0,0 @@
|
||||
<template>
|
||||
<div class="page-footer">
|
||||
<div class="page-footer-link">
|
||||
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 官网 </a>
|
||||
<a href="https://github.com/jekip/naive-ui-admin" target="_blank"> 社区 </a>
|
||||
<a href="https://github.com/jekip/naive-ui-admin/issues" target="_blank"> 交流 </a>
|
||||
</div>
|
||||
<div class="copyright"> naive-ui-admin 1.4 · Made by Ah jung </div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageFooter',
|
||||
components: {},
|
||||
props: {
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-footer {
|
||||
//margin: 28px 0 24px 0;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
font-size: 14px;
|
||||
color: #808695;
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
color: #515a6e;
|
||||
}
|
||||
}
|
||||
|
||||
&-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
a:not(:last-child) {
|
||||
margin-right: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.copyright {
|
||||
color: #808695;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -58,10 +58,6 @@
|
||||
<MainView />
|
||||
</div>
|
||||
</div>
|
||||
<!--1.15废弃,没啥用,占用操作空间-->
|
||||
<!-- <NLayoutFooter v-if="getShowFooter">-->
|
||||
<!-- <PageFooter />-->
|
||||
<!-- </NLayoutFooter>-->
|
||||
</n-layout-content>
|
||||
<n-back-top :right="100" />
|
||||
</n-layout>
|
||||
|
||||
@@ -5,7 +5,7 @@ import router, { setupRouter } from './router';
|
||||
import { setupStore } from '@/store';
|
||||
import { setupNaive, setupDirectives } from '@/plugins';
|
||||
import { AppProvider } from '@/components/Application';
|
||||
import Websocket from '@/utils/websocket';
|
||||
import setupWebsocket from '@/utils/websocket/index';
|
||||
|
||||
async function bootstrap() {
|
||||
const appProvider = createApp(AppProvider);
|
||||
@@ -36,15 +36,7 @@ async function bootstrap() {
|
||||
// 路由准备就绪后挂载APP实例
|
||||
await router.isReady();
|
||||
|
||||
// 全局websocket
|
||||
const onMessageList: Array<Function> = [];
|
||||
app.provide('onMessageList', onMessageList);
|
||||
const onMessage = (event: any) => {
|
||||
onMessageList.forEach((f) => {
|
||||
f.call(null, event);
|
||||
});
|
||||
};
|
||||
Websocket(onMessage);
|
||||
setupWebsocket();
|
||||
|
||||
app.mount('#app', true);
|
||||
}
|
||||
|
||||
186
web/src/utils/highHtml.ts
Normal file
186
web/src/utils/highHtml.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
// 关键词配置
|
||||
interface IKeywordOption {
|
||||
keyword: string | RegExp;
|
||||
color?: string;
|
||||
bgColor?: string;
|
||||
style?: Record<string, any>;
|
||||
// 高亮标签名
|
||||
tagName?: string;
|
||||
// 忽略大小写
|
||||
caseSensitive?: boolean;
|
||||
// 自定义渲染高亮html
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
renderHighlightKeyword?: (content: string) => any;
|
||||
}
|
||||
|
||||
type IKeyword = string | IKeywordOption;
|
||||
|
||||
export interface IMatchIndex {
|
||||
index: number;
|
||||
subString: string;
|
||||
}
|
||||
|
||||
// 关键词索引
|
||||
export interface IKeywordParseIndex {
|
||||
keyword: string | RegExp;
|
||||
indexList: IMatchIndex[];
|
||||
option?: IKeywordOption;
|
||||
}
|
||||
|
||||
// 关键词
|
||||
export interface IKeywordParseResult {
|
||||
start: number;
|
||||
end: number;
|
||||
subString?: string;
|
||||
option?: IKeywordOption;
|
||||
}
|
||||
|
||||
// 计算
|
||||
const getKeywordIndexList = (content: string, keyword: string | RegExp, flags = 'ig') => {
|
||||
const reg = new RegExp(keyword, flags);
|
||||
const res = (content as any).matchAll(reg);
|
||||
const arr = [...res];
|
||||
const allIndexArr: IMatchIndex[] = arr.map((e) => ({
|
||||
index: e.index,
|
||||
subString: e['0'],
|
||||
}));
|
||||
return allIndexArr;
|
||||
};
|
||||
|
||||
// 驼峰转换横线
|
||||
function humpToLine(name: string) {
|
||||
return name.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
}
|
||||
|
||||
const renderNodeTag = (subStr: string, option: IKeywordOption) => {
|
||||
const s = subStr;
|
||||
if (!option) {
|
||||
return s;
|
||||
}
|
||||
const { tagName = 'mark', bgColor, color, style = {}, renderHighlightKeyword } = option;
|
||||
if (typeof renderHighlightKeyword === 'function') {
|
||||
return renderHighlightKeyword(subStr);
|
||||
}
|
||||
style.backgroundColor = bgColor;
|
||||
style.color = color;
|
||||
|
||||
const styleContent = Object.keys(style)
|
||||
.map((k) => `${humpToLine(k)}:${style[k]}`)
|
||||
.join(';');
|
||||
const styleStr = `style="${styleContent}"`;
|
||||
return `<${tagName} ${styleStr}>${s}</${tagName}>`;
|
||||
};
|
||||
|
||||
const renderHighlightHtml = (content: string, list: any[]) => {
|
||||
let str = '';
|
||||
list.forEach((item) => {
|
||||
const { start, end, option } = item;
|
||||
const s = content.slice(start, end);
|
||||
const subStr = renderNodeTag(s, option);
|
||||
str += subStr;
|
||||
item.subString = subStr;
|
||||
});
|
||||
return str;
|
||||
};
|
||||
|
||||
// 解析关键词为索引
|
||||
const parseHighlightIndex = (content: string, keywords: IKeyword[]) => {
|
||||
const result: IKeywordParseIndex[] = [];
|
||||
keywords.forEach((keywordOption: IKeyword) => {
|
||||
let option: IKeywordOption = { keyword: '' };
|
||||
if (typeof keywordOption === 'string') {
|
||||
option = { keyword: keywordOption };
|
||||
} else {
|
||||
option = keywordOption;
|
||||
}
|
||||
const { keyword, caseSensitive = true } = option;
|
||||
const indexList = getKeywordIndexList(content, keyword, caseSensitive ? 'g' : 'gi');
|
||||
const res = {
|
||||
keyword,
|
||||
indexList,
|
||||
option,
|
||||
};
|
||||
result.push(res);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
const parseHighlightString = (content: string, keywords: IKeyword[]) => {
|
||||
const result = parseHighlightIndex(content, keywords);
|
||||
const splitList: IKeywordParseResult[] = [];
|
||||
const findSplitIndex = (index: number, len: number) => {
|
||||
for (let i = 0; i < splitList.length; i++) {
|
||||
const cur = splitList[i];
|
||||
// 有交集
|
||||
if (
|
||||
(index > cur.start && index < cur.end) ||
|
||||
(index + len > cur.start && index + len < cur.end) ||
|
||||
(cur.start > index && cur.start < index + len) ||
|
||||
(cur.end > index && cur.end < index + len) ||
|
||||
(index === cur.start && index + len === cur.end)
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
// 没有交集,且在当前的前面
|
||||
if (index + len <= cur.start) {
|
||||
return i;
|
||||
}
|
||||
// 没有交集,且在当前的后面的,放在下个迭代处理
|
||||
}
|
||||
return splitList.length;
|
||||
};
|
||||
result.forEach(({ indexList, option }: IKeywordParseIndex) => {
|
||||
indexList.forEach((e) => {
|
||||
const { index, subString } = e;
|
||||
const item = {
|
||||
start: index,
|
||||
end: index + subString.length,
|
||||
option,
|
||||
};
|
||||
const splitIndex = findSplitIndex(index, subString.length);
|
||||
if (splitIndex !== -1) {
|
||||
splitList.splice(splitIndex, 0, item);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 补上没有匹配关键词的部分
|
||||
const list: IKeywordParseResult[] = [];
|
||||
splitList.forEach((cur, i) => {
|
||||
const { start, end } = cur;
|
||||
const next = splitList[i + 1];
|
||||
// 第一个前面补一个
|
||||
if (i === 0 && start > 0) {
|
||||
list.push({ start: 0, end: start, subString: content.slice(0, start) });
|
||||
}
|
||||
list.push({ ...cur, subString: content.slice(start, end) });
|
||||
// 当前和下一个中间补一个
|
||||
if (next?.start > end) {
|
||||
list.push({
|
||||
start: end,
|
||||
end: next.start,
|
||||
subString: content.slice(end, next.start),
|
||||
});
|
||||
}
|
||||
// 最后一个后面补一个
|
||||
if (i === splitList.length - 1 && end < content.length - 1) {
|
||||
list.push({
|
||||
start: end,
|
||||
end: content.length - 1,
|
||||
subString: content.slice(end, content.length),
|
||||
});
|
||||
}
|
||||
});
|
||||
return list;
|
||||
};
|
||||
|
||||
// 生成关键词高亮的html字符串
|
||||
const highHtml = (content: string, keywords: IKeyword[]) => {
|
||||
const splitList = parseHighlightString(content, keywords);
|
||||
return {
|
||||
highText: renderHighlightHtml(content, splitList),
|
||||
highList: splitList,
|
||||
};
|
||||
};
|
||||
|
||||
export default highHtml;
|
||||
@@ -84,3 +84,17 @@ export function timeFix() {
|
||||
? '下午好'
|
||||
: '晚上好';
|
||||
}
|
||||
|
||||
// 随机浅色
|
||||
export function rdmLightRgbColor(): string {
|
||||
const letters = '456789ABCDEF';
|
||||
let color = '#';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
if (i === 0) {
|
||||
color += 'F'; // 确保第一个字符较亮
|
||||
} else {
|
||||
color += letters[Math.floor(Math.random() * letters.length)];
|
||||
}
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios';
|
||||
import axios from 'axios';
|
||||
import { AxiosCanceler } from './axiosCancel';
|
||||
import { isFunction } from '@/utils/is';
|
||||
import { isFunction, isString, isUrl } from '@/utils/is';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
|
||||
import { ContentTypeEnum } from '@/enums/httpEnum';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
|
||||
export * from './axiosTransform';
|
||||
|
||||
@@ -107,19 +108,29 @@ export class VAxios {
|
||||
/**
|
||||
* @description: 文件上传
|
||||
*/
|
||||
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
|
||||
const formData = new window.FormData();
|
||||
const customFilename = params.name || 'file';
|
||||
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams, options?: RequestOptions) {
|
||||
const transform = this.getTransform();
|
||||
const { requestCatch, transformRequestData } = transform || {};
|
||||
const { requestOptions } = this.options;
|
||||
const opt: RequestOptions = Object.assign({}, requestOptions, options);
|
||||
|
||||
if (params.filename) {
|
||||
formData.append(customFilename, params.file, params.filename);
|
||||
} else {
|
||||
formData.append(customFilename, params.file);
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
const apiUrl = globSetting.apiUrl || '';
|
||||
const isUrlStr = isUrl(config.url as string);
|
||||
|
||||
if (!isUrlStr) {
|
||||
config.url = `${urlPrefix}${config.url}`;
|
||||
}
|
||||
|
||||
if (params.data) {
|
||||
Object.keys(params.data).forEach((key) => {
|
||||
const value = params.data![key];
|
||||
if (!isUrlStr && apiUrl && isString(apiUrl)) {
|
||||
config.url = `${apiUrl}${config.url}`;
|
||||
}
|
||||
|
||||
const formData = new window.FormData();
|
||||
if (params) {
|
||||
Object.keys(params).forEach((key) => {
|
||||
const value = params![key];
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => {
|
||||
formData.append(`${key}[]`, item);
|
||||
@@ -127,18 +138,42 @@ export class VAxios {
|
||||
return;
|
||||
}
|
||||
|
||||
formData.append(key, params.data![key]);
|
||||
formData.append(key, params![key]);
|
||||
});
|
||||
}
|
||||
|
||||
return this.axiosInstance.request<T>({
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-type': ContentTypeEnum.FORM_DATA,
|
||||
ignoreCancelToken: true,
|
||||
},
|
||||
...config,
|
||||
return new Promise((resolve, reject) => {
|
||||
this.axiosInstance
|
||||
.request<T>({
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-type': ContentTypeEnum.FORM_DATA,
|
||||
ignoreCancelToken: true,
|
||||
},
|
||||
...config,
|
||||
})
|
||||
.then((res: AxiosResponse<Result>) => {
|
||||
// 请求是否被取消
|
||||
const isCancel = axios.isCancel(res);
|
||||
if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
|
||||
try {
|
||||
const ret = transformRequestData(res, opt);
|
||||
resolve(ret);
|
||||
} catch (err) {
|
||||
reject(err || new Error('request error!'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
resolve(res as unknown as Promise<T>);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
if (requestCatch && isFunction(requestCatch)) {
|
||||
reject(requestCatch(e));
|
||||
return;
|
||||
}
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,69 +1,21 @@
|
||||
import { SocketEnum } from '@/enums/socketEnum';
|
||||
import { notificationStoreWidthOut } from '@/store/modules/notification';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||
import { isJsonString } from '@/utils/is';
|
||||
import { registerGlobalMessage } from '@/utils/websocket/registerMessage';
|
||||
|
||||
// WebSocket消息格式
|
||||
export interface WebSocketMessage {
|
||||
event: string;
|
||||
data: any;
|
||||
code: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
let socket: WebSocket;
|
||||
let isActive: boolean;
|
||||
const messageHandler: Map<string, Function> = new Map();
|
||||
|
||||
export function getSocket(): WebSocket {
|
||||
if (socket === undefined) {
|
||||
location.reload();
|
||||
}
|
||||
return socket;
|
||||
}
|
||||
|
||||
export function getActive(): boolean {
|
||||
return isActive;
|
||||
}
|
||||
|
||||
export function sendMsg(event: string, data = null, isRetry = true) {
|
||||
if (socket === undefined || !isActive) {
|
||||
if (!isRetry) {
|
||||
console.log('socket连接异常,发送失败!');
|
||||
return;
|
||||
}
|
||||
console.log('socket连接异常,等待重试..');
|
||||
setTimeout(function () {
|
||||
sendMsg(event, data);
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
event: event,
|
||||
data: data,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
// @ts-ignore
|
||||
console.log('ws发送消息失败,等待重试,err:' + err.message);
|
||||
if (!isRetry) {
|
||||
return;
|
||||
}
|
||||
setTimeout(function () {
|
||||
sendMsg(event, data);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
export function addOnMessage(onMessageList: any, func: Function) {
|
||||
let exist = false;
|
||||
for (let i = 0; i < onMessageList.length; i++) {
|
||||
if (onMessageList[i].name == func.name) {
|
||||
onMessageList[i] = func;
|
||||
exist = true;
|
||||
}
|
||||
}
|
||||
if (!exist) {
|
||||
onMessageList.push(func);
|
||||
}
|
||||
}
|
||||
|
||||
export default (onMessage: Function) => {
|
||||
export default () => {
|
||||
const heartCheck = {
|
||||
timeout: 5000,
|
||||
timeoutObj: setTimeout(() => {}),
|
||||
@@ -85,35 +37,37 @@ export default (onMessage: Function) => {
|
||||
})
|
||||
);
|
||||
self.serverTimeoutObj = setTimeout(function () {
|
||||
console.log('关闭服务');
|
||||
console.log('[WebSocket] 关闭服务');
|
||||
socket.close();
|
||||
}, self.timeout);
|
||||
}, this.timeout);
|
||||
},
|
||||
};
|
||||
|
||||
const notificationStore = notificationStoreWidthOut();
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
let lockReconnect = false;
|
||||
let timer: ReturnType<typeof setTimeout>;
|
||||
const createSocket = () => {
|
||||
console.log('createSocket...');
|
||||
console.log('[WebSocket] createSocket...');
|
||||
if (useUserStore.token === '') {
|
||||
console.error('[WebSocket] 用户未登录,稍后重试...');
|
||||
reconnect();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (useUserStore.token === '') {
|
||||
throw new Error('用户未登录,稍后重试...');
|
||||
}
|
||||
socket = new WebSocket(useUserStore.config?.wsAddr + '?authorization=' + useUserStore.token);
|
||||
socket = new WebSocket(`${useUserStore.config?.wsAddr}?authorization=${useUserStore.token}`);
|
||||
init();
|
||||
} catch (e) {
|
||||
console.log('createSocket err:' + e);
|
||||
console.error(`[WebSocket] createSocket err: ${e}`);
|
||||
reconnect();
|
||||
}
|
||||
if (lockReconnect) {
|
||||
lockReconnect = false;
|
||||
}
|
||||
};
|
||||
|
||||
const reconnect = () => {
|
||||
console.log('lockReconnect:' + lockReconnect);
|
||||
console.log('[WebSocket] lockReconnect:' + lockReconnect);
|
||||
if (lockReconnect) return;
|
||||
lockReconnect = true;
|
||||
clearTimeout(timer);
|
||||
@@ -124,7 +78,7 @@ export default (onMessage: Function) => {
|
||||
|
||||
const init = () => {
|
||||
socket.onopen = function (_) {
|
||||
console.log('WebSocket:已连接');
|
||||
console.log('[WebSocket] 已连接');
|
||||
heartCheck.reset().start();
|
||||
isActive = true;
|
||||
};
|
||||
@@ -133,47 +87,25 @@ export default (onMessage: Function) => {
|
||||
isActive = true;
|
||||
// console.log('WebSocket:收到一条消息', event.data);
|
||||
|
||||
let isHeart = false;
|
||||
if (!isJsonString(event.data)) {
|
||||
console.log('socket message incorrect format:' + JSON.stringify(event));
|
||||
console.log('[WebSocket] message incorrect format:' + JSON.stringify(event));
|
||||
return;
|
||||
}
|
||||
|
||||
const message = JSON.parse(event.data);
|
||||
if (message.event === 'ping') {
|
||||
isHeart = true;
|
||||
}
|
||||
|
||||
// 强制退出
|
||||
if (message.event === 'kick') {
|
||||
useUserStore.logout().then(() => {
|
||||
// 移除标签页
|
||||
localStorage.removeItem(TABS_ROUTES);
|
||||
location.reload();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 通知
|
||||
if (message.event === 'notice') {
|
||||
notificationStore.triggerNewMessages(message.data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onMessage && !isHeart) {
|
||||
onMessage.call(null, event);
|
||||
}
|
||||
heartCheck.reset().start();
|
||||
|
||||
const message = JSON.parse(event.data) as WebSocketMessage;
|
||||
onMessage(message);
|
||||
};
|
||||
|
||||
socket.onerror = function (_) {
|
||||
console.log('WebSocket:发生错误');
|
||||
console.log('[WebSocket] 发生错误');
|
||||
reconnect();
|
||||
isActive = false;
|
||||
};
|
||||
|
||||
socket.onclose = function (_) {
|
||||
console.log('WebSocket:已关闭');
|
||||
console.log('[WebSocket] 已关闭');
|
||||
heartCheck.reset();
|
||||
reconnect();
|
||||
isActive = false;
|
||||
@@ -186,4 +118,63 @@ export default (onMessage: Function) => {
|
||||
};
|
||||
|
||||
createSocket();
|
||||
registerGlobalMessage();
|
||||
};
|
||||
|
||||
function onMessage(message: WebSocketMessage) {
|
||||
let handled = false;
|
||||
messageHandler.forEach((value: Function, key: string) => {
|
||||
if (message.event === key || key === '*') {
|
||||
handled = true;
|
||||
value.call(null, message);
|
||||
}
|
||||
});
|
||||
|
||||
if (!handled) {
|
||||
console.log('[WebSocket] messageHandler not registered. message:' + JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
export function sendMsg(event: string, data: any = null, isRetry = true) {
|
||||
if (socket === undefined || !isActive) {
|
||||
if (!isRetry) {
|
||||
console.log('[WebSocket] 连接异常,发送失败!');
|
||||
return;
|
||||
}
|
||||
console.log('[WebSocket] 连接异常,等待重试..');
|
||||
setTimeout(() => {
|
||||
sendMsg(event, data);
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
socket.send(JSON.stringify({ event, data }));
|
||||
} catch (err: any) {
|
||||
console.log('[WebSocket] 发送消息失败,err:', err.message);
|
||||
if (!isRetry) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[WebSocket] 等待重试..');
|
||||
setTimeout(() => {
|
||||
sendMsg(event, data);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加消息处理
|
||||
export function addOnMessage(key: string, value: Function): void {
|
||||
messageHandler.set(key, value);
|
||||
}
|
||||
|
||||
// 移除消息处理
|
||||
export function removeOnMessage(key: string): boolean {
|
||||
return messageHandler.delete(key);
|
||||
}
|
||||
|
||||
// 查看所有消息处理
|
||||
export function getAllOnMessage(): Map<string, Function> {
|
||||
return messageHandler;
|
||||
}
|
||||
32
web/src/utils/websocket/registerMessage.ts
Normal file
32
web/src/utils/websocket/registerMessage.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||
import { SocketEnum } from '@/enums/socketEnum';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import { notificationStoreWidthOut } from '@/store/modules/notification';
|
||||
import { addOnMessage, WebSocketMessage } from '@/utils/websocket/index';
|
||||
|
||||
// 注册全局消息监听
|
||||
export function registerGlobalMessage() {
|
||||
// 心跳
|
||||
addOnMessage(SocketEnum.EventPing, function (_message: WebSocketMessage) {
|
||||
// console.log('ping..');
|
||||
});
|
||||
|
||||
// 强制退出
|
||||
addOnMessage(SocketEnum.EventKick, function (_message: WebSocketMessage) {
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
useUserStore.logout().then(() => {
|
||||
// 移除标签页
|
||||
localStorage.removeItem(TABS_ROUTES);
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
// 消息通知
|
||||
addOnMessage(SocketEnum.EventNotice, function (message: WebSocketMessage) {
|
||||
const notificationStore = notificationStoreWidthOut();
|
||||
notificationStore.triggerNewMessages(message.data);
|
||||
});
|
||||
|
||||
// 更多全局消息处理都可以在这里注册
|
||||
// ...
|
||||
}
|
||||
42
web/src/views/addons/hgexample/comp/calendar/index.vue
Normal file
42
web/src/views/addons/hgexample/comp/calendar/index.vue
Normal file
@@ -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/esm';
|
||||
|
||||
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>
|
||||
89
web/src/views/addons/hgexample/comp/des/index.vue
Normal file
89
web/src/views/addons/hgexample/comp/des/index.vue
Normal file
@@ -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>
|
||||
58
web/src/views/addons/hgexample/comp/directive/index.vue
Normal file
58
web/src/views/addons/hgexample/comp/directive/index.vue
Normal file
@@ -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>
|
||||
162
web/src/views/addons/hgexample/comp/drag/index.vue
Normal file
162
web/src/views/addons/hgexample/comp/drag/index.vue
Normal file
@@ -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>
|
||||
38
web/src/views/addons/hgexample/comp/fingerprintjs/index.vue
Normal file
38
web/src/views/addons/hgexample/comp/fingerprintjs/index.vue
Normal file
@@ -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>
|
||||
175
web/src/views/addons/hgexample/comp/form/basic.vue
Normal file
175
web/src/views/addons/hgexample/comp/form/basic.vue
Normal file
@@ -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>
|
||||
198
web/src/views/addons/hgexample/comp/form/useForm.vue
Normal file
198
web/src/views/addons/hgexample/comp/form/useForm.vue
Normal file
@@ -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>
|
||||
23
web/src/views/addons/hgexample/comp/icons/antd.vue
Normal file
23
web/src/views/addons/hgexample/comp/icons/antd.vue
Normal file
@@ -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>
|
||||
136
web/src/views/addons/hgexample/comp/icons/icons.vue
Normal file
136
web/src/views/addons/hgexample/comp/icons/icons.vue
Normal file
@@ -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>
|
||||
23
web/src/views/addons/hgexample/comp/icons/ionicons5.vue
Normal file
23
web/src/views/addons/hgexample/comp/icons/ionicons5.vue
Normal file
@@ -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>
|
||||
29
web/src/views/addons/hgexample/comp/icons/selector.vue
Normal file
29
web/src/views/addons/hgexample/comp/icons/selector.vue
Normal file
@@ -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>
|
||||
68
web/src/views/addons/hgexample/comp/import/excel.vue
Normal file
68
web/src/views/addons/hgexample/comp/import/excel.vue
Normal file
@@ -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/index.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>
|
||||
369
web/src/views/addons/hgexample/comp/index.vue
Normal file
369
web/src/views/addons/hgexample/comp/index.vue
Normal file
@@ -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>
|
||||
33
web/src/views/addons/hgexample/comp/map/baidu.vue
Normal file
33
web/src/views/addons/hgexample/comp/map/baidu.vue
Normal file
@@ -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>
|
||||
34
web/src/views/addons/hgexample/comp/map/gaode.vue
Normal file
34
web/src/views/addons/hgexample/comp/map/gaode.vue
Normal file
@@ -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>
|
||||
307
web/src/views/addons/hgexample/comp/modal/index.vue
Normal file
307
web/src/views/addons/hgexample/comp/modal/index.vue
Normal file
@@ -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>
|
||||
15
web/src/views/addons/hgexample/comp/moreComponents/index.vue
Normal file
15
web/src/views/addons/hgexample/comp/moreComponents/index.vue
Normal file
@@ -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>
|
||||
322
web/src/views/addons/hgexample/comp/notice/index.vue
Normal file
322
web/src/views/addons/hgexample/comp/notice/index.vue
Normal file
@@ -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>
|
||||
109
web/src/views/addons/hgexample/comp/print/index.vue
Normal file
109
web/src/views/addons/hgexample/comp/print/index.vue
Normal file
@@ -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>
|
||||
70
web/src/views/addons/hgexample/comp/result/fail.vue
Normal file
70
web/src/views/addons/hgexample/comp/result/fail.vue
Normal file
@@ -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>
|
||||
74
web/src/views/addons/hgexample/comp/result/info.vue
Normal file
74
web/src/views/addons/hgexample/comp/result/info.vue
Normal file
@@ -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>
|
||||
55
web/src/views/addons/hgexample/comp/result/success.vue
Normal file
55
web/src/views/addons/hgexample/comp/result/success.vue
Normal file
@@ -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>
|
||||
26
web/src/views/addons/hgexample/comp/tagsView/index.vue
Normal file
26
web/src/views/addons/hgexample/comp/tagsView/index.vue
Normal file
@@ -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>
|
||||
70
web/src/views/addons/hgexample/comp/text/gradient/index.vue
Normal file
70
web/src/views/addons/hgexample/comp/text/gradient/index.vue
Normal file
@@ -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>
|
||||
94
web/src/views/addons/hgexample/comp/text/high/index.vue
Normal file
94
web/src/views/addons/hgexample/comp/text/high/index.vue
Normal file
@@ -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>
|
||||
50
web/src/views/addons/hgexample/comp/text/mint/index.vue
Normal file
50
web/src/views/addons/hgexample/comp/text/mint/index.vue
Normal file
@@ -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>
|
||||
102
web/src/views/addons/hgexample/comp/text/pinyin/index.vue
Normal file
102
web/src/views/addons/hgexample/comp/text/pinyin/index.vue
Normal file
@@ -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>
|
||||
97
web/src/views/addons/hgexample/comp/timeline/index.vue
Normal file
97
web/src/views/addons/hgexample/comp/timeline/index.vue
Normal file
@@ -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>
|
||||
15
web/src/views/addons/hgexample/comp/vis/echarts.vue
Normal file
15
web/src/views/addons/hgexample/comp/vis/echarts.vue
Normal file
@@ -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>
|
||||
15
web/src/views/addons/hgexample/comp/vis/madeapie.vue
Normal file
15
web/src/views/addons/hgexample/comp/vis/madeapie.vue
Normal file
@@ -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>
|
||||
15
web/src/views/addons/hgexample/comp/vis/ppchart.vue
Normal file
15
web/src/views/addons/hgexample/comp/vis/ppchart.vue
Normal file
@@ -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>
|
||||
106
web/src/views/addons/hgexample/comp/waterfall/index.vue
Normal file
106
web/src/views/addons/hgexample/comp/waterfall/index.vue
Normal file
@@ -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>
|
||||
171
web/src/views/addons/hgexample/comp/watermark/index.vue
Normal file
171
web/src/views/addons/hgexample/comp/watermark/index.vue
Normal file
@@ -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>
|
||||
@@ -7,7 +7,7 @@
|
||||
</n-input-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="二维码" path="index">
|
||||
<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>
|
||||
@@ -24,10 +24,12 @@
|
||||
|
||||
interface Props {
|
||||
path: string;
|
||||
showQrcode?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
path: '',
|
||||
showQrcode: true,
|
||||
});
|
||||
|
||||
const copy = () => {
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
<Form path="/addons/hgexample/default" />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="websocket" tab="Websocket">
|
||||
<Form path="/socket/hgexample/index/test?name=HotGo" />
|
||||
<Form path="/socket/hgexample/index/test?name=HotGo" :showQrcode="false" />
|
||||
<WebsocketTest />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
@@ -24,6 +25,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Form from './form.vue';
|
||||
import WebsocketTest from './websocketTest.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
134
web/src/views/addons/hgexample/portal/websocketTest.vue
Normal file
134
web/src/views/addons/hgexample/portal/websocketTest.vue
Normal file
@@ -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>
|
||||
@@ -21,11 +21,11 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="handleUpload">
|
||||
<n-button type="primary" @click="handleUpload" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<UploadOutlined />
|
||||
@@ -33,26 +33,31 @@
|
||||
</template>
|
||||
上传文件
|
||||
</n-button>
|
||||
|
||||
<n-button type="primary" @click="handleUploadImage">
|
||||
<n-button type="success" @click="handleMultipartUpload" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<UploadOutlined />
|
||||
<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">
|
||||
<n-button type="primary" @click="handleUploadDoc" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<UploadOutlined />
|
||||
<FileWordOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
上传文档
|
||||
</n-button>
|
||||
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled" class="ml-2">
|
||||
<template #icon>
|
||||
<n-icon>
|
||||
<DeleteOutlined />
|
||||
@@ -67,6 +72,7 @@
|
||||
<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" @onFinish="handleFinishCall" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -77,8 +83,15 @@
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { Delete, List } from '@/api/apply/attachment';
|
||||
import { columns, schemas } from './columns';
|
||||
import { DeleteOutlined, UploadOutlined } from '@vicons/antd';
|
||||
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';
|
||||
|
||||
const message = useMessage();
|
||||
@@ -90,9 +103,10 @@
|
||||
const fileUploadRef = ref();
|
||||
const imageUploadRef = ref();
|
||||
const docUploadRef = ref();
|
||||
const multipartUploadRef = ref();
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 150,
|
||||
width: 120,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -132,6 +146,10 @@
|
||||
docUploadRef.value.openModal();
|
||||
}
|
||||
|
||||
function handleMultipartUpload() {
|
||||
multipartUploadRef.value.openModal();
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="primary" @click="addTable" class="min-left-space">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Option } from '@/api/creditsLog';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import {Dicts} from "@/api/dict/dict";
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
@@ -240,7 +241,9 @@ export const columns = [
|
||||
];
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Option();
|
||||
options.value = await Dicts({
|
||||
types: ['creditType', 'creditGroup'],
|
||||
});
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'creditType':
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<n-tab-pane
|
||||
:name="item.key.toString()"
|
||||
:tab="item.label"
|
||||
v-for="item in options.status"
|
||||
v-for="item in options.orderStatus"
|
||||
:key="item.key"
|
||||
>
|
||||
<List :type="defaultTab" />
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
:resizeHeightOffset="-10000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Option } from '@/api/order';
|
||||
import { isNullObject } from '@/utils/is';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { getOptionLabel, getOptionTag, Options } from '@/utils/hotgo';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
|
||||
export interface State {
|
||||
id: number;
|
||||
@@ -49,7 +50,7 @@ export function newState(state: State | null): State {
|
||||
}
|
||||
|
||||
export const options = ref<Options>({
|
||||
status: [],
|
||||
orderStatus: [],
|
||||
acceptRefundStatus: [],
|
||||
payType: [],
|
||||
});
|
||||
@@ -170,12 +171,12 @@ export const columns = [
|
||||
style: {
|
||||
marginRight: '6px',
|
||||
},
|
||||
type: getOptionTag(options.value.status, row.status),
|
||||
type: getOptionTag(options.value.orderStatus, row.status),
|
||||
bordered: false,
|
||||
},
|
||||
{
|
||||
default: () =>
|
||||
getOptionLabel(options.value.status, row.status) +
|
||||
getOptionLabel(options.value.orderStatus, row.status) +
|
||||
(row.status === 9 ? ',' + row.rejectRefundReason : ''),
|
||||
}
|
||||
);
|
||||
@@ -190,11 +191,13 @@ export const columns = [
|
||||
];
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Option();
|
||||
options.value = await Dicts({
|
||||
types: ['payType', 'orderStatus', 'acceptRefundStatus'],
|
||||
});
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.status;
|
||||
item.componentProps.options = options.value.orderStatus;
|
||||
break;
|
||||
case 'acceptRefundStatus':
|
||||
item.componentProps.options = options.value.acceptRefundStatus;
|
||||
|
||||
@@ -23,7 +23,7 @@ export class State {
|
||||
public image = ''; // 单图
|
||||
public attachfile = ''; // 附件
|
||||
public cityId = 0; // 所在城市
|
||||
public switch = 1; // 显示开关
|
||||
public switch = 2; // 显示开关
|
||||
public sort = 0; // 排序
|
||||
public status = 1; // 状态
|
||||
public createdBy = 0; // 创建者
|
||||
@@ -31,7 +31,12 @@ export class State {
|
||||
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) {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
export const genInfoObj = {
|
||||
label: '',
|
||||
name: '',
|
||||
group: 1,
|
||||
version: 'v1.0.0',
|
||||
brief: '',
|
||||
description: '',
|
||||
author: '',
|
||||
};
|
||||
|
||||
export const selectListObj = {
|
||||
groupType: [],
|
||||
status: [],
|
||||
};
|
||||
|
||||
export function newState(state) {
|
||||
if (state !== null) {
|
||||
return cloneDeep(state);
|
||||
}
|
||||
return cloneDeep(genInfoObj);
|
||||
}
|
||||
@@ -60,7 +60,7 @@
|
||||
>
|
||||
<n-form-item label="插件标签" path="label">
|
||||
<n-input placeholder="请输入" v-model:value="formParams.label" />
|
||||
<template #feedback>显示在插件列表中的标识. 不要超过20个字符</template>
|
||||
<template #feedback>显示在插件列表中的模块名称. 不要超过20个字符</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="插件包名" path="name">
|
||||
@@ -73,12 +73,25 @@
|
||||
<n-form-item label="功能分组" path="group">
|
||||
<n-select
|
||||
placeholder="请选择"
|
||||
:options="selectList.groupType"
|
||||
:options="options.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 options.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>
|
||||
@@ -114,24 +127,14 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { h, onBeforeMount, reactive, ref } from 'vue';
|
||||
import {
|
||||
NIcon,
|
||||
NTag,
|
||||
NIconWrapper,
|
||||
useMessage,
|
||||
NImage,
|
||||
useDialog,
|
||||
useNotification,
|
||||
} from 'naive-ui';
|
||||
import { h, reactive, ref } from 'vue';
|
||||
import { NIcon, useMessage, useDialog, useNotification } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, FormSchema, useForm } from '@/components/Form/index';
|
||||
import { List, Selects, Build, UnInstall, Install, Upgrade } from '@/api/develop/addons';
|
||||
import { PlusOutlined } from '@vicons/antd';
|
||||
import { newState } from '@/views/develop/addons/components/model';
|
||||
import { errorImg } from '@/utils/hotgo';
|
||||
import { isUrl } from '@/utils/is';
|
||||
import { getIconComponent } from '@/utils/icons';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { List, Build, UnInstall, Install, Upgrade } from '@/api/develop/addons';
|
||||
import { PlusOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
||||
import { newState, schemas, columns, options } from './model';
|
||||
import { adaModalWidth } from '@/utils/hotgo';
|
||||
|
||||
const dialog = useDialog();
|
||||
const message = useMessage();
|
||||
@@ -145,158 +148,6 @@
|
||||
const checkedIds = ref([]);
|
||||
const searchFormRef = ref<any>();
|
||||
|
||||
const selectList = ref({
|
||||
groupType: [],
|
||||
status: [],
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '图标',
|
||||
key: 'logo',
|
||||
width: 80,
|
||||
render(row) {
|
||||
if (isUrl(row.logo)) {
|
||||
return h(NImage, {
|
||||
width: 48,
|
||||
height: 48,
|
||||
src: row.logo,
|
||||
fallbackSrc: errorImg,
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
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: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '安装状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
title: '操作',
|
||||
@@ -345,7 +196,7 @@
|
||||
}
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
mapWidth();
|
||||
adaModalWidth(dialogWidth);
|
||||
return await List({ ...res, ...searchFormRef.value?.formModel });
|
||||
};
|
||||
|
||||
@@ -365,9 +216,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -383,9 +231,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -401,9 +246,6 @@
|
||||
reloadTable();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -437,9 +279,6 @@
|
||||
buildSuccessNotify();
|
||||
});
|
||||
},
|
||||
onNegativeClick: () => {
|
||||
// message.error('取消');
|
||||
},
|
||||
});
|
||||
} else {
|
||||
message.error('请填写完整信息');
|
||||
@@ -448,32 +287,6 @@
|
||||
});
|
||||
}
|
||||
|
||||
function mapWidth() {
|
||||
let val = document.body.clientWidth;
|
||||
const def = 840; // 默认宽度
|
||||
if (val < def) {
|
||||
dialogWidth.value = '100%';
|
||||
} else {
|
||||
dialogWidth.value = def + 'px';
|
||||
}
|
||||
|
||||
return dialogWidth.value;
|
||||
}
|
||||
|
||||
const loadSelect = async () => {
|
||||
selectList.value = await Selects({});
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = selectList.value.status;
|
||||
break;
|
||||
case 'group':
|
||||
item.componentProps.options = selectList.value.groupType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function onUpdateValueGroup(value) {
|
||||
formParams.value.group = value;
|
||||
}
|
||||
@@ -500,10 +313,6 @@
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await loadSelect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
203
web/src/views/develop/addons/model.ts
Normal file
203
web/src/views/develop/addons/model.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { h, ref } from 'vue';
|
||||
import { Dicts } from '@/api/dict/dict';
|
||||
import { errorImg, Option, Options } 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';
|
||||
|
||||
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: errorImg,
|
||||
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: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
component: 'NSelect',
|
||||
label: '安装状态',
|
||||
componentProps: {
|
||||
placeholder: '请选择状态',
|
||||
options: [],
|
||||
onUpdateValue: (e: any) => {
|
||||
console.log(e);
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export interface IOptions extends Options {
|
||||
addonsGroupOptions: Option[];
|
||||
addonsInstallStatus: Option[];
|
||||
addonsExtend: Option[];
|
||||
}
|
||||
|
||||
export const options = ref<IOptions>({
|
||||
addonsGroupOptions: [],
|
||||
addonsInstallStatus: [],
|
||||
addonsExtend: [],
|
||||
});
|
||||
|
||||
async function loadOptions() {
|
||||
options.value = await Dicts({
|
||||
types: ['addonsGroupOptions', 'addonsInstallStatus', 'addonsExtend'],
|
||||
});
|
||||
for (const item of schemas.value) {
|
||||
switch (item.field) {
|
||||
case 'status':
|
||||
item.componentProps.options = options.value.addonsInstallStatus;
|
||||
break;
|
||||
case 'group':
|
||||
item.componentProps.options = options.value.addonsGroupOptions;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await loadOptions();
|
||||
@@ -42,7 +42,7 @@
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { genInfoObj, selectListObj } from '@/views/develop/code/components/model';
|
||||
import { ColumnList } from '@/api/develop/code';
|
||||
import { NButton, NCheckbox, NInput, NSelect, NTooltip, NTreeSelect } from 'naive-ui';
|
||||
import { NButton, NCheckbox, NInput, NSelect, NTooltip, NTreeSelect,NCascader } from 'naive-ui';
|
||||
import { HelpCircleOutline, Reload } from '@vicons/ionicons5';
|
||||
import { renderIcon } from '@/utils';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
@@ -278,10 +278,15 @@
|
||||
key: 'dictType',
|
||||
width: 300,
|
||||
render(row) {
|
||||
return h(NTreeSelect, {
|
||||
value: row.dictType,
|
||||
disabled: row.name === 'id',
|
||||
if (row.dictType == 0){
|
||||
row.dictType = null;
|
||||
}
|
||||
return h(NCascader, {
|
||||
placeholder: '请选择字典类型',
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
disabled: row.name === 'id',
|
||||
value: row.dictType,
|
||||
options: props.selectList?.dictMode ?? [],
|
||||
onUpdateValue: function (e) {
|
||||
row.dictType = e;
|
||||
|
||||
@@ -210,7 +210,7 @@
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
title: '菜单名称',
|
||||
title: '生成名称',
|
||||
key: 'tableComment',
|
||||
width: 140,
|
||||
},
|
||||
|
||||
@@ -43,6 +43,14 @@
|
||||
loading.value = true;
|
||||
init();
|
||||
});
|
||||
|
||||
function open(src: string) {
|
||||
frameSrc.value = src;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
:resizeHeightOffset="-20000"
|
||||
>
|
||||
<template #tableTitle>
|
||||
@@ -82,7 +82,7 @@
|
||||
const checkedIds = ref([]);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 150,
|
||||
width: 180,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="error" @click="batchDelete" :disabled="batchDeleteDisabled">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button type="info" @click="openGroupModal">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
:actionColumn="actionColumn"
|
||||
:checked-row-keys="checkedIds"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
:resizeHeightOffset="-10000"
|
||||
size="small"
|
||||
>
|
||||
@@ -123,7 +123,7 @@
|
||||
const showModal = ref(false);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 180,
|
||||
width: 150,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
|
||||
@@ -162,11 +162,11 @@ export const columns = [
|
||||
key: 'content',
|
||||
width: 320,
|
||||
},
|
||||
{
|
||||
title: '调用行',
|
||||
key: 'line',
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// title: '调用行',
|
||||
// key: 'line',
|
||||
// width: 150,
|
||||
// },
|
||||
{
|
||||
title: '触发时间',
|
||||
key: 'triggerNs',
|
||||
|
||||
@@ -93,9 +93,9 @@
|
||||
import DataItem from './components/DataItem.vue';
|
||||
import LoadChart from './components/chart/LoadChart.vue';
|
||||
import FullYearSalesChart from './components/chart/FullYearSalesChart.vue';
|
||||
import { defineComponent, inject, onMounted, ref, onUnmounted } from 'vue';
|
||||
import { defineComponent, onMounted, ref, onUnmounted } from 'vue';
|
||||
import { SocketEnum } from '@/enums/socketEnum';
|
||||
import { addOnMessage, sendMsg } from '@/utils/websocket';
|
||||
import { addOnMessage, removeOnMessage, sendMsg, WebSocketMessage } from '@/utils/websocket';
|
||||
import { formatBefore } from '@/utils/dateUtil';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
|
||||
@@ -179,35 +179,32 @@
|
||||
const loading = ref(true);
|
||||
const loadChartRef = ref<InstanceType<typeof LoadChart>>();
|
||||
const fullYearSalesChartRef = ref<InstanceType<typeof FullYearSalesChart>>();
|
||||
const onMessageList = inject('onMessageList');
|
||||
|
||||
const onAdminMonitor = (res: { data: string }) => {
|
||||
const data = JSON.parse(res.data);
|
||||
if (data.event === SocketEnum.EventAdminMonitorRunInfo) {
|
||||
loading.value = false;
|
||||
if (data.code == SocketEnum.CodeErr) {
|
||||
message.error('查询出错:' + data.event);
|
||||
return;
|
||||
}
|
||||
|
||||
dataRunInfo.value = data.data;
|
||||
// 运行信息
|
||||
const onMessageAdminMonitorRunInfo = (res: WebSocketMessage) => {
|
||||
loading.value = false;
|
||||
if (res.code == SocketEnum.CodeErr) {
|
||||
message.error('查询出错:' + res.event);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.event === SocketEnum.EventAdminMonitorTrends) {
|
||||
loading.value = false;
|
||||
if (data.code == SocketEnum.CodeErr) {
|
||||
message.error('查询出错:' + data.event);
|
||||
return;
|
||||
}
|
||||
dataSource.value = data.data;
|
||||
return;
|
||||
}
|
||||
dataRunInfo.value = res.data;
|
||||
};
|
||||
|
||||
addOnMessage(onMessageList, onAdminMonitor);
|
||||
// 服务器信息
|
||||
const onMessageAdminMonitorTrends = (res: WebSocketMessage) => {
|
||||
loading.value = false;
|
||||
if (res.code == SocketEnum.CodeErr) {
|
||||
message.error('查询出错:' + res.event);
|
||||
return;
|
||||
}
|
||||
dataSource.value = res.data;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
addOnMessage(SocketEnum.EventAdminMonitorRunInfo, onMessageAdminMonitorRunInfo);
|
||||
addOnMessage(SocketEnum.EventAdminMonitorTrends, onMessageAdminMonitorTrends);
|
||||
|
||||
loading.value = true;
|
||||
sendMsg(SocketEnum.EventAdminMonitorTrends);
|
||||
sendMsg(SocketEnum.EventAdminMonitorRunInfo);
|
||||
@@ -231,6 +228,8 @@
|
||||
|
||||
onUnmounted(() => {
|
||||
window.clearInterval(timer.value);
|
||||
removeOnMessage(SocketEnum.EventAdminMonitorTrends);
|
||||
removeOnMessage(SocketEnum.EventAdminMonitorRunInfo);
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
:scroll-x="1800"
|
||||
:scroll-x="1280"
|
||||
>
|
||||
<template #tableTitle>
|
||||
<n-button
|
||||
@@ -288,7 +288,7 @@
|
||||
});
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
width: 240,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getPostOption } from '@/api/org/post';
|
||||
import { FormSchema, useForm } from '@/components/Form';
|
||||
import { statusOptions } from '@/enums/optionsiEnum';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import {Dicts} from "@/api/dict/dict";
|
||||
|
||||
// 增加余额/积分.
|
||||
|
||||
@@ -213,14 +214,10 @@ export async function loadOptions() {
|
||||
treeDataToCompressed(role.list);
|
||||
}
|
||||
|
||||
const post = await getPostOption();
|
||||
if (post.list && post.list.length > 0) {
|
||||
for (let i = 0; i < post.list.length; i++) {
|
||||
post.list[i].label = post.list[i].name;
|
||||
post.list[i].value = post.list[i].id;
|
||||
}
|
||||
options.value.post = post.list;
|
||||
}
|
||||
const tmpOptions = await Dicts({
|
||||
types: ['adminPostOption'],
|
||||
});
|
||||
options.value.post =tmpOptions?.adminPostOption;
|
||||
}
|
||||
|
||||
function treeDataToCompressed(source) {
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
<template #suffix> MB</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
<n-form-item label="图片类型限制" path="uploadImageType">
|
||||
<n-input v-model:value="formValue.uploadImageType" placeholder="" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="文件大小限制" path="uploadFileSize">
|
||||
<n-input-number
|
||||
@@ -39,6 +36,11 @@
|
||||
<template #suffix> MB</template>
|
||||
</n-input-number>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="图片类型限制" path="uploadImageType">
|
||||
<n-input v-model:value="formValue.uploadImageType" placeholder="" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="文件类型限制" path="uploadFileType">
|
||||
<n-input v-model:value="formValue.uploadFileType" placeholder="" />
|
||||
</n-form-item>
|
||||
|
||||
Reference in New Issue
Block a user