版本预发布

This commit is contained in:
孟帅
2023-02-08 20:29:34 +08:00
parent f11c7c5bf2
commit 2068d05c93
269 changed files with 16122 additions and 12075 deletions

View File

@@ -0,0 +1,105 @@
<template>
<n-cascader
v-bind="$props"
:value="valueLabel"
:options="dataOptions"
:placeholder="placeholder"
:check-strategy="checkStrategy"
clearable
cascade
:on-update:value="onValueChange"
:on-load="handleLoad"
:on-focus="focusLoad"
remote
/>
</template>
<script lang="ts" setup>
import { GetCityLabel, ProvincesSelect } from '@/api/apply/provinces';
import { computed, ref, watch } from 'vue';
import type { CascaderOption } from 'naive-ui';
const emits = defineEmits(['update:value', 'update:label']);
import { basicProps } from './props';
const props = defineProps({
...basicProps,
});
const valueLabel = ref<string | null>(null);
const dataOptions = ref([]);
const placeholder = computed(() => {
if (props.dataType === 'p') {
return '请选择省份';
} else if (props.dataType === 'pc') {
return '请选择省市';
} else {
return '请选择省市区';
}
});
function onValueChange(
value: string | number | Array<string | number> | null,
option: CascaderOption | Array<CascaderOption | null> | null,
pathValues: Array<CascaderOption | null>
) {
const tempPathValues = pathValues
? pathValues.map((it: CascaderOption | null) => ({
label: it?.label,
value: it?.value,
level: it?.level,
}))
: null;
emits('update:value', value);
valueLabel.value = getLabel(tempPathValues);
}
function getLabel(values): string | null {
if (values === null || values === undefined) {
return null;
}
let label = '';
const length = values.length;
for (let i = 0; i < length; i++) {
const item = values[i];
label += item.label;
if (i + 1 < length) {
label += props.separator;
}
}
return label;
}
watch(
() => props.value,
async () => {
if (props.value === 0) {
valueLabel.value = null;
return;
}
if (valueLabel.value === null) {
valueLabel.value = await GetCityLabel({ id: props.value, spilt: props.separator });
}
},
{
immediate: true,
deep: true,
}
);
async function load(option) {
const data = await ProvincesSelect({ dataType: props.dataType, ...option });
return data.list;
}
async function handleLoad(option: CascaderOption) {
option.children = await load({ dataType: props.dataType, ...option });
return;
}
async function focusLoad() {
if (dataOptions.value.length === 0) {
dataOptions.value = await load({ dataType: props.dataType });
}
}
</script>

View File

@@ -0,0 +1,22 @@
import type { PropType } from 'vue';
import { NCascader } from 'naive-ui';
export const basicProps = {
...NCascader.props,
defaultValue: {
type: [Number, String, Array],
default: null,
},
value: {
type: [Number, String, Array],
default: null,
},
dataType: {
type: String as PropType<'p' | 'pc' | 'pca'>,
default: 'pca',
},
checkStrategy: {
type: String as PropType<'child' | 'all'>,
default: 'child',
},
};

View File

@@ -1,11 +1,15 @@
<template>
<QuillEditor
ref="quillEditor"
:options="options"
toolbar="full"
v-model:content="content"
@ready="readyQuill"
class="quillEditor"
:id="quillEditorId"
:id="id"
:modules="modules"
@focus="onEditorFocus"
@blur="onEditorBlur"
@update:content="onUpdateContent"
/>
</template>
@@ -13,40 +17,27 @@
import { ref, watch, onMounted } from 'vue';
import { QuillEditor } from '@vueup/vue-quill';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import ImageUploader from 'quill-image-uploader';
import MagicUrl from 'quill-magic-url';
import { getRandomString } from '@/utils/charset';
import { UploadImage } from '@/api/base';
import componentSetting from '@/settings/componentSetting';
import { isNullOrUnDef } from '@/utils/is';
import { useMessage } from 'naive-ui';
export interface Props {
value: string;
id?: string;
}
const emit = defineEmits(['update:value']);
const quillEditorId = ref('quillEditorId-' + getRandomString(16, true));
const message = useMessage();
const initFinish = ref(false);
const quillEditor = ref();
const content = ref();
const props = withDefaults(defineProps<Props>(), { value: '' });
const options = ref({
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{ header: 1 }, { header: 2 }], // custom button values
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }], // superscript/subscript
[{ indent: '-1' }, { indent: '+1' }], // outdent/indent
[{ direction: 'rtl' }], // text direction
[{ size: ['small', false, 'large', 'huge'] }], // custom dropdown
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }], // dropdown with defaults from theme
[{ font: [] }],
[{ align: [] }],
['clean'],
['image'],
],
},
theme: 'snow',
placeholder: '输入您要编辑的内容!',
const props = withDefaults(defineProps<Props>(), {
value: '',
id: 'quillEditorId-' + getRandomString(16, true),
});
function readyQuill() {
@@ -54,20 +45,41 @@
}
watch(
() => content.value,
(_newValue, _oldValue) => {
if (quillEditor.value !== undefined) {
emit('update:value', quillEditor.value.getHTML());
() => props.value,
(newValue) => {
if (!initFinish.value) {
quillEditor.value?.setHTML(newValue);
}
},
{
immediate: true, // 深度监听
immediate: true,
deep: true,
}
);
function onEditorFocus(val) {
initFinish.value = true;
console.log(val);
}
function onEditorBlur(val) {
console.log(val);
}
function onUpdateContent() {
emit('update:value', quillEditor.value.getHTML());
}
function checkFileType(map: string[], fileType: string) {
if (isNullOrUnDef(map)) {
return true;
}
return map.includes(fileType);
}
onMounted(async () => {
// 兼容表单分组 n-form-item-blank
let dom = document.getElementById(quillEditorId.value);
let dom = document.getElementById(props.id);
if (dom && dom.parentNode) {
const parent = dom.parentNode as Element;
if ('n-form-item-blank' === parent.className) {
@@ -75,10 +87,64 @@
}
}
});
const modules = [
{
name: 'imageUploader',
module: ImageUploader,
options: {
upload: (file) => {
return new Promise((resolve, reject) => {
if (!checkFileType(componentSetting.upload.imageType, file.type)) {
message.error(`只能上传图片类型为${componentSetting.upload.imageType.join(',')}`);
reject('Upload failed');
return;
}
const formData = new FormData();
formData.append('file', file);
UploadImage(formData)
.then((res) => {
console.log(res);
resolve(res.fileUrl);
})
.catch((err) => {
reject('Upload failed');
console.error('Error:', err);
});
});
},
},
},
{
name: 'magicUrl',
module: MagicUrl,
},
];
</script>
<style lang="less">
.ql-container {
<style lang="less" scoped>
:deep(.ql-container) {
height: auto;
}
:deep(.ql-container.ql-snow) {
border: none;
}
:deep(.ql-toolbar.ql-snow) {
border: none;
border-bottom: 1px solid #ccc;
}
:deep(.ql-editor.ql-blank::before) {
color: #afb4bd;
font-size: 14px;
font-style: normal;
}
.dark .priview-content {
background: #5a5a5a;
color: #fff;
}
.light .priview-content {
background: #fff;
color: #333;
}
</style>

View File

@@ -60,7 +60,7 @@
v-bind="getComponentProps(schema)"
:is="schema.component"
v-model:value="formModel[schema.field]"
:class="{ isFull: schema.isFull != false && getProps.isFull }"
:class="{ isFull: schema.isFull !== false && getProps.isFull }"
/>
<!--组件后面的内容-->
<template v-if="schema.suffix">
@@ -75,8 +75,8 @@
</n-gi>
<!--提交 重置 展开 收起 按钮-->
<n-gi
:span="isInline ? '' : 24"
:suffix="isInline ? true : false"
:span="isInline ? 1 : 24"
:suffix="!!isInline"
#="{ overflow }"
v-if="getProps.showActionButtonGroup"
>
@@ -134,7 +134,7 @@
import type { GridProps } from 'naive-ui/lib/grid';
import type { FormSchema, FormProps, FormActionType } from './types/form';
import { isArray } from '@/utils/is/index';
import { isArray } from '@/utils/is';
import { deepMerge } from '@/utils';
export default defineComponent({

View File

@@ -2,7 +2,7 @@
<div class="tableAction">
<div class="flex items-center justify-center">
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
<n-button v-bind="action" class="mx-2">
<n-button v-bind="action" class="mx-1">
{{ action.label }}
<template #icon v-if="action.hasOwnProperty('icon')">
<n-icon :component="action.icon" />
@@ -16,16 +16,14 @@
@select="select"
>
<slot name="more"></slot>
<n-button v-bind="getMoreProps" class="mx-2" v-if="!$slots.more" icon-placement="right">
<n-button v-bind="getMoreProps" class="mx-1" v-if="!$slots.more" icon-placement="right">
<div class="flex items-center">
<span>更多</span>
<n-icon size="14" class="ml-1">
<DownOutlined />
</n-icon>
</div>
<!-- <template #icon>-->
<!-- -->
<!-- </template>-->
<!-- <template #icon> </template>-->
</n-button>
</n-dropdown>
</div>
@@ -33,7 +31,7 @@
</template>
<script lang="ts">
import { defineComponent, PropType, computed, toRaw } from 'vue';
import { computed, defineComponent, PropType, toRaw } from 'vue';
import { ActionItem } from '@/components/Table';
import { usePermission } from '@/hooks/web/usePermission';
import { isBoolean, isFunction } from '@/utils/is';
@@ -87,7 +85,7 @@
return {
size: 'small',
text: actionText,
type: actionType,
type: getBtnType(action), //actionType,
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
@@ -110,6 +108,28 @@
return isIfShow;
}
function getBtnType(action) {
if (action.type !== undefined && action.type !== '') {
return action.type;
}
switch (action.label) {
case '编辑':
return 'primary';
case '启用':
case '已禁用':
return 'warning';
case '已启用':
case '禁用':
return 'success';
case '删除':
return 'error';
case '查看详情':
return 'default';
default:
return 'primary';
}
}
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
@@ -121,7 +141,7 @@
return {
size: 'small',
text: actionText,
type: actionType,
type: getBtnType(action), //actionType,
...action,
...(popConfirm || {}),
onConfirm: popConfirm?.confirm,

View File

@@ -215,7 +215,6 @@
}
//勾选列
function onSelection(e) {
console.log('onSelection:' + JSON.stringify(e));
let checkList = table.getCacheColumns();
if (e) {
checkList.unshift({ type: 'selection', key: 'selection' });

View File

@@ -1,7 +1,7 @@
import { ref, ComputedRef, unref, computed, onMounted, watchEffect, watch } from 'vue';
import type { BasicTableProps } from '../types/table';
import type { PaginationProps } from '../types/pagination';
import { isBoolean, isFunction, isArray } from '@/utils/is';
import { isBoolean, isFunction } from '@/utils/is';
import { APISETTING } from '../const';
export function useDataSource(
@@ -31,8 +31,8 @@ export function useDataSource(
return rowKey
? rowKey
: () => {
return 'key';
};
return 'key';
};
});
const getDataSourceRef = computed(() => {

View File

@@ -1,82 +1,84 @@
<template>
<div class="w-full">
<div class="upload">
<div class="upload-card">
<!--图片列表-->
<div
class="upload-card-item"
:style="getCSSProperties"
v-for="(item, index) in imgList"
:key="`img_${index}`"
>
<div class="upload-card-item-info">
<div class="img-box">
<template v-if="fileType === 'image'">
<img :src="item" @error="errorImg($event)" />
</template>
<template v-else>
<n-avatar :style="fileAvatarCSS">{{ getFileExt(item) }}</n-avatar>
</template>
</div>
<div class="img-box-actions">
<template v-if="fileType === 'image'">
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
<EyeOutlined />
<div>
<div class="w-full">
<div class="upload">
<div class="upload-card">
<!--图片列表-->
<div
class="upload-card-item"
:style="getCSSProperties"
v-for="(item, index) in imgList"
:key="`img_${index}`"
>
<div class="upload-card-item-info">
<div class="img-box">
<template v-if="fileType === 'image'">
<img :src="item" @error="errorImg($event)" />
</template>
<template v-else>
<n-avatar :style="fileAvatarCSS">{{ getFileExt(item) }}</n-avatar>
</template>
</div>
<div class="img-box-actions">
<template v-if="fileType === 'image'">
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
<EyeOutlined />
</n-icon>
</template>
<template v-else>
<n-icon size="18" class="mx-2 action-icon" @click="download(item)">
<CloudDownloadOutlined />
</n-icon>
</template>
<n-icon size="18" class="mx-2 action-icon" @click="remove(index)">
<DeleteOutlined />
</n-icon>
</template>
<template v-else>
<n-icon size="18" class="mx-2 action-icon" @click="download(item)">
<CloudDownloadOutlined />
</n-icon>
</template>
<n-icon size="18" class="mx-2 action-icon" @click="remove(index)">
<DeleteOutlined />
</n-icon>
</div>
</div>
</div>
</div>
<!--上传图片-->
<div
class="upload-card-item upload-card-item-select-picture"
:style="getCSSProperties"
v-if="imgList.length < maxNumber"
>
<n-upload
v-bind="$props"
:file-list-style="{ display: 'none' }"
@before-upload="beforeUpload"
@finish="finish"
<!--上传图片-->
<div
class="upload-card-item upload-card-item-select-picture"
:style="getCSSProperties"
v-if="imgList.length < maxNumber"
>
<div class="flex flex-col justify-center">
<n-icon size="18" class="m-auto">
<PlusOutlined />
</n-icon>
<span class="upload-title">{{ uploadTitle }}</span>
</div>
</n-upload>
<n-upload
v-bind="$props"
:file-list-style="{ display: 'none' }"
@before-upload="beforeUpload"
@finish="finish"
>
<div class="flex flex-col justify-center">
<n-icon size="18" class="m-auto">
<PlusOutlined />
</n-icon>
<span class="upload-title">{{ uploadTitle }}</span>
</div>
</n-upload>
</div>
</div>
</div>
<!--上传图片-->
<n-space>
<n-alert title="提示" type="info" v-if="helpText" class="flex w-full">
{{ helpText }}
</n-alert>
</n-space>
</div>
<!--上传图片-->
<n-space>
<n-alert title="提示" type="info" v-if="helpText" class="flex w-full">
{{ helpText }}
</n-alert>
</n-space>
<!--预览图片-->
<n-modal
v-model:show="showModal"
preset="card"
title="预览"
:bordered="false"
:style="{ width: '520px' }"
>
<img :src="previewUrl" />
</n-modal>
</div>
<!--预览图片-->
<n-modal
v-model:show="showModal"
preset="card"
title="预览"
:bordered="false"
:style="{ width: '520px' }"
>
<img :src="previewUrl" />
</n-modal>
</template>
<script lang="ts">
@@ -87,7 +89,7 @@
import { ResultEnum } from '@/enums/httpEnum';
import componentSetting from '@/settings/componentSetting';
import { useGlobSetting } from '@/hooks/setting';
import { isJsonString, isNullOrUnDef } from '@/utils/is';
import { isArray, isJsonString, isNullOrUnDef } from '@/utils/is';
import { getFileExt } from '@/utils/urlUtils';
import { errorImg } from '@/utils/hotgo';
const globSetting = useGlobSetting();
@@ -131,6 +133,10 @@
() => {
loadValue(props.value);
return;
},
{
immediate: true,
deep: true,
}
);
@@ -139,12 +145,16 @@
() => {
loadValue(props.values);
return;
},
{
immediate: true,
deep: true,
}
);
// 加载默认
function loadValue(value: any) {
if (value === null) {
if (value === undefined || value === null) {
return;
}
@@ -163,6 +173,10 @@
data = value;
}
if (!isArray(data) || data.length === 0) {
return;
}
state.imgList = data.map((item) => {
return getImgUrl(item);
});

View File

@@ -9,7 +9,7 @@ export const basicProps = {
},
accept: {
type: String,
default: '.jpg,.png,.jpeg,.svg,.gif',
default: '.jpg,.png,.jpeg,.svg,.gif,.webp',
},
helpText: {
type: String as PropType<string>,

View File

@@ -1,21 +1,25 @@
<template>
<BasicUpload
:action="`${uploadUrl}${urlPrefix}/upload/file`"
:headers="uploadHeaders"
:data="{ type: 0 }"
name="file"
:width="100"
:height="100"
fileType="file"
:maxNumber="maxNumber"
@uploadChange="uploadChange"
v-model:value="image"
v-model:values="images"
/>
<div>
<BasicUpload
:action="`${uploadUrl}${urlPrefix}/upload/file`"
:headers="uploadHeaders"
:data="{ type: 0 }"
accept="*"
name="file"
:width="100"
:height="100"
fileType="file"
:maxNumber="maxNumber"
:helpText="helpText"
@uploadChange="uploadChange"
v-model:value="image"
v-model:values="images"
/>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, unref, reactive } from 'vue';
import { ref, onMounted, unref, reactive, watch } from 'vue';
import { BasicUpload } from '@/components/Upload';
import { useGlobSetting } from '@/hooks/setting';
import { useUserStoreWidthOut } from '@/store/modules/user';
@@ -23,6 +27,7 @@
export interface Props {
value: string | string[] | null;
maxNumber: number;
helpText?: string;
}
const globSetting = useGlobSetting();
@@ -33,7 +38,7 @@
Authorization: useUserStore.token,
});
const emit = defineEmits(['update:value']);
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1 });
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1, helpText: '' });
const image = ref<string>('');
const images = ref<string[] | object>([]);
@@ -47,12 +52,27 @@
}
}
onMounted(async () => {
function loadImage() {
if (props.maxNumber === 1) {
image.value = props.value as string;
} else {
images.value = props.value as string[];
}
}
watch(
() => props.value,
() => {
loadImage();
},
{
immediate: true,
deep: true,
}
);
onMounted(async () => {
loadImage();
});
</script>

View File

@@ -7,6 +7,7 @@
:width="100"
:height="100"
:maxNumber="maxNumber"
:helpText="helpText"
@uploadChange="uploadChange"
v-model:value="image"
v-model:values="images"
@@ -14,7 +15,7 @@
</template>
<script lang="ts" setup>
import { ref, onMounted, unref, reactive } from 'vue';
import { onMounted, reactive, ref, unref, watch } from 'vue';
import { BasicUpload } from '@/components/Upload';
import { useGlobSetting } from '@/hooks/setting';
import { useUserStoreWidthOut } from '@/store/modules/user';
@@ -22,6 +23,7 @@
export interface Props {
value: string | string[] | null;
maxNumber: number;
helpText?: string;
}
const globSetting = useGlobSetting();
@@ -32,7 +34,7 @@
Authorization: useUserStore.token,
});
const emit = defineEmits(['update:value']);
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1 });
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1, helpText: '' });
const image = ref<string>('');
const images = ref<string[]>([]);
@@ -46,12 +48,28 @@
}
}
onMounted(async () => {
//赋值默认图片显示
function loadImage() {
if (props.maxNumber === 1) {
image.value = props.value as string;
} else {
images.value = props.value as string[];
}
}
watch(
() => props.value,
() => {
loadImage();
},
{
immediate: true,
deep: true,
}
);
onMounted(async () => {
loadImage();
});
</script>