合并冲突

This commit is contained in:
赵帅 2022-10-02 16:49:59 +08:00
commit 30d4f31265
70 changed files with 13804 additions and 1774 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

2
.gitignore vendored
View File

@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
*.lock
# Editor directories and files
.idea
@ -20,3 +21,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?

11
.vscode/settings.json vendored
View File

@ -1,3 +1,12 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[typescriptreact]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[vue]": {
"editor.defaultFormatter": "Wscats.vue"
}
}

View File

@ -4,5 +4,7 @@
],
"version": "0.0.0",
"useWorkspaces": true,
"npmClient": "yarn"
"npmClient": "yarn",
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useNx": false
}

View File

@ -18,13 +18,46 @@
"eslint-plugin-vue": "^9.4.0",
"husky": "^8.0.0",
"intersection-observer": "^0.12.2",
"lerna": "^4.0.0",
"lerna": "^5.5.4",
"lint-staged": "^13.0.0",
"npm-run-all": "^4.1.5",
"stylelint": "^14.11.0",
"stylelint-config-recommended-scss": "^7.0.0",
"stylelint-config-standard": "^28.0.0",
"stylelint-scss": "^3.3.1"
"stylelint-scss": "^3.3.1",
"@vitejs/plugin-vue": "^3.1.0",
"@vitejs/plugin-vue-jsx": "^2.0.1",
"@vue/babel-plugin-jsx": "^1.1.1",
"typescript": "^4.6.4",
"vite": "^3.1.0",
"vue-tsc": "^0.40.4",
"@vue/test-utils": "^2.0.0",
"@babel/parser": "^7.19.0",
"@babel/preset-env": "^7.19.0",
"@babel/preset-typescript": "^7.18.0",
"@babel/traverse": "^7.19.0",
"@types/chalk": "^2.2.0",
"@types/commander": "^2.12.2",
"@types/ora": "^3.2.0",
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"@vue/compiler-sfc": "^3.2.0",
"@vuedx/typecheck": "^0.7.5",
"@vuedx/typescript-plugin-vue": "^0.7.5",
"babel-jest": "^29.0.3",
"chalk": "^5.0.0",
"commander": "^9.4.0",
"conventional-changelog-cli": "^2.2.2",
"inquirer": "^9.1.1",
"jest": "^29.0.0",
"ora": "^6.1.2",
"patch-vue-directive-ssr": "^0.0.1",
"sass": "^1.32.2",
"shelljs": "^0.8.4",
"vite-plugin-md": "^0.20.0",
"vite-svg-loader": "^3.6.0",
"vitepress": "0.20.1",
"vitepress-theme-demoblock": "1.3.2"
},
"lint-staged": {
"packages/docs-vue/{*.vue,*.js,*.ts,*.jsx,*.tsx}": "eslint --fix",

BIN
packages/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
import HelloWorld from './components/HelloWorld.vue';
</script>
<template>

View File

@ -1,9 +1,11 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>();
const count = ref(0)
</script>
<template>

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import { ref } from 'vue'
defineProps<{ msg: string }>();
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

View File

@ -1,7 +1,8 @@
/// <reference types="vite/client" />
// / <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
import type { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}

BIN
packages/eslint-config/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -1,15 +0,0 @@
import type { App } from 'vue';
import According from './src/according.component';
import AccordingItem from './src/components/according-item.component';
export * from './src/according.props';
export * from './src/components/according-item.props';
export { According, AccordingItem };
export default {
install(app: App): void {
app.component(According.name, According);
app.component(AccordingItem.name, AccordingItem);
}
};

View File

@ -11,15 +11,28 @@ export default defineComponent({
setup(props: AccordingProps, context: SetupContext) {
const accordingStyle = computed(() => ({
height: props.height ? `${props.height}px` : '',
width: props.width ? `${props.width}px` : '',
width: props.width ? `${props.width}px` : ''
}));
const accordingClass = computed(() => {
const customClassArray = props.customClass;
const accordingClassObject = {
'farris-panel': true,
according: true
};
customClassArray.reduce<Record<string, unknown>>((classObject, classString) => {
classObject[classString] = true;
return classObject;
}, accordingClassObject);
return accordingClassObject;
});
return () => {
return (
<div class={`farris-panel accoriding ${props.customClass.join(' ')}`} style={accordingStyle.value}>
<div class={accordingClass.value} style={accordingStyle.value}>
{context.slots.default && context.slots.default()}
</div>
);
};
},
}
});

View File

@ -1,4 +1,4 @@
/* 很重要 */
.farris-panel {
border: 1px solid rgba(0, 0, 0, .125)
border: 1px solid rgba(0, 0, 0, 0.125);
}

View File

@ -1,10 +1,9 @@
import { ExtractPropTypes } from 'vue';
export const accordingItemProps = {
width:{type:Number},
height:{type:Number},
title:{type:String,default:''},
disable:{type:Boolean,default:false}
width: { type: Number },
height: { type: Number },
title: { type: String, default: '' },
disable: { type: Boolean, default: false }
};
export type AccordingItemProps = ExtractPropTypes<typeof accordingItemProps>;

View File

@ -0,0 +1,15 @@
import type { App } from 'vue';
import Accordion from './src/accordion.component';
import AccordionItem from './src/components/accordion-item.component';
export * from './src/accordion.props';
export * from './src/components/accordion-item.props';
export { Accordion, AccordionItem };
export default {
install(app: App): void {
app.component(Accordion.name, Accordion);
app.component(AccordionItem.name, AccordionItem);
}
};

View File

@ -0,0 +1,38 @@
import { isContext } from 'vm';
import { computed, defineComponent, SetupContext } from 'vue';
import { AccordionProps, accordionProps } from './accordion.props';
import './accordion.css';
export default defineComponent({
name: 'FAccordion',
props: accordionProps,
emits: [],
setup(props: AccordionProps, context: SetupContext) {
const accordionStyle = computed(() => ({
height: props.height ? `${props.height}px` : '',
width: props.width ? `${props.width}px` : ''
}));
const accordionClass = computed(() => {
const customClassArray = props.customClass;
const accordionClassObject = {
'farris-panel': true,
according: true
};
customClassArray.reduce<Record<string, unknown>>((classObject, classString) => {
classObject[classString] = true;
return classObject;
}, accordionClassObject);
return accordionClassObject;
});
return () => {
return (
<div class={accordionClass.value} style={accordionStyle.value}>
{context.slots.default && context.slots.default()}
</div>
);
};
}
});

View File

@ -0,0 +1,4 @@
/* 很重要 */
.farris-panel {
border: 1px solid rgba(0, 0, 0, 0.125);
}

View File

@ -1,10 +1,10 @@
import { ExtractPropTypes } from 'vue';
export const accordingProps = {
export const accordionProps = {
customClass: { type: Array<string>, default: [] },
height: { type: Number },
width: { type: Number },
enableFold: { type: Boolean, default: true },
expanded: { type: Boolean, default: false },
expanded: { type: Boolean, default: false }
};
export type AccordingProps = ExtractPropTypes<typeof accordingProps>;
export type AccordionProps = ExtractPropTypes<typeof accordionProps>;

View File

@ -0,0 +1,25 @@
.card-header {
cursor: pointer;
}
.panel-item-title {
float: left;
}
.panel-item-tool {
float: right;
}
.panel-item-clear {
clear: both;
}
.f-state-disable {
pointer-events: none;
}
/* 很重要 */
.card {
border-left: 0;
border-right: 0;
}

View File

@ -1,25 +1,28 @@
import { computed, defineComponent, ref, SetupContext } from 'vue';
import { accordionItemProps, AccordionItemProps } from './accordion-item.props';
import './accordion-item.scss';
export default defineComponent({
name: 'FAccordingItem',
props: {},
name: 'FAccordionItem',
props: accordionItemProps,
emits: [],
setup(props, context: SetupContext) {
const title = ref('');
setup(props: AccordionItemProps, context: SetupContext) {
const title = ref(props.title);
const isActive = ref(false);
const isDisabled = ref(false);
function selectAccordingItem() {}
function selectAccordionItem() {}
function onClick($event: Event) {
selectAccordingItem();
selectAccordionItem();
}
const accordingItemClass = computed(() => ({
const accordionItemClass = computed(() => ({
'f-state-disable': isDisabled.value,
card: true,
'farris-panel-item': true,
'f-state-selected': isActive.value,
'f-state-selected': isActive.value
}));
const shouldShowHeader = computed(() => {
@ -32,13 +35,13 @@ export default defineComponent({
const headIconClass = computed(() => ({
'f-icon': true,
'f-according-collapse': !isActive.value,
'f-according-expand': isActive.value,
'f-accordion-collapse': !isActive.value,
'f-accordion-expand': isActive.value
}));
return () => {
return (
<div class={accordingItemClass.value}>
<div class={accordionItemClass.value}>
<div class="card-header" onClick={onClick}>
<div class="panel-item-title">
{shouldShowHeader.value && <span>{title.value}</span>}
@ -48,11 +51,11 @@ export default defineComponent({
<div class="panel-item-tool">{context.slots.toolbar && context.slots.toolbar()}</div>
<div class="panel-item-clear"></div>
</div>
<div dropAnimation="active?'active':'inactive'">
<div>
<div class="card-body">{context.slots.content && context.slots.content()}</div>
</div>
</div>
);
};
},
}
});

View File

@ -0,0 +1,9 @@
import { ExtractPropTypes } from 'vue';
export const accordionItemProps = {
width: { type: Number },
height: { type: Number },
title: { type: String, default: '' },
disable: { type: Boolean, default: false }
};
export type AccordionItemProps = ExtractPropTypes<typeof accordionItemProps>;

View File

@ -0,0 +1,25 @@
.card-header {
cursor: pointer;
}
.panel-item-title {
float: left;
}
.panel-item-tool {
float: right;
}
.panel-item-clear {
clear: both;
}
.f-state-disable {
pointer-events: none;
}
/* 很重要 */
.card {
border-left: 0;
border-right: 0;
}

View File

@ -1,10 +1,13 @@
import { defineComponent, computed, ref, SetupContext } from 'vue';
import { avatarProps, AvatarProps } from './avatar.props';
import { useImage } from './composition/use-image';
import './avatar.scss';
export default defineComponent({
name: 'Avatar',
props: avatarProps,
emits: ['change'],
emits: ['change', 'update:modelValue'],
setup(props: AvatarProps, context: SetupContext) {
const avatarClass = computed(() => ({
'f-avatar': true,
@ -13,6 +16,8 @@ export default defineComponent({
'f-avatar-square': props.shape === 'square'
}));
const modelValue = ref(props.modelValue);
const avatarStyle = computed(() => ({
width: props.avatarWidth + 'px',
height: props.avatarHeight + 'px'
@ -29,32 +34,30 @@ export default defineComponent({
function getfiledata() {}
const defaultImgSrc =
'data:image/jpeg;base64,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';
const errorImgSrc =
'data:image/jpeg;base64,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';
const file = ref(null);
const imageType = computed(() => props.type.join());
const { acceptTypes, imageSource, onClickImage } = useImage(props, context, file, modelValue);
return () => {
return (
<div class={avatarClass.value} style={avatarStyle.value}>
<div class={avatarClass.value} style={avatarStyle.value} onClick={onClickImage}>
{showLoading && (
<div class="f-avatar-upload-loading">
<div class="loading-inner"></div>
</div>
)}
<img title={props.tile} class="f-avatar-image" src={imgSrc} onError={errorSrc()} />
<img title={props.tile} class="f-avatar-image" src={imageSource.value} onError={errorSrc()} />
{!props.readonly && (
<div class="f-avatar-icon">
<span class="f-icon f-icon-camera"></span>
</div>
)}
<input
ref="file"
name="file-input"
type="file"
class="f-avatar-upload"
accept={imageType.value}
accept={acceptTypes.value}
onChange={getfiledata}
style="display: none;"
/>

View File

@ -27,6 +27,10 @@ export const avatarProps = {
* , MB
*/
maxSize: { type: Number, default: 1 },
/**
*
*/
modelValue: { type: String, default: '' },
/**
*
*/
@ -34,7 +38,7 @@ export const avatarProps = {
/**
*
*/
type: { type: Array<string>, default: [] },
type: { type: Array<string>, default: [] }
};
export type AvatarProps = ExtractPropTypes<typeof avatarProps>;

View File

@ -0,0 +1,70 @@
.f-avatar {
position: relative;
// width: 100%;
// height: 100%;
cursor: pointer;
overflow: hidden;
&.f-avatar-readonly {
cursor: default;
}
&.f-avatar-circle {
border-radius: 100%;
overflow: hidden;
}
&.f-avatar-square {
border-radius: 0;
}
.f-avatar-image,
.f-avatar-defult {
display: inline-block;
width: 100%;
height: 100%;
}
.f-avatar-icon {
display: none;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.3);
.f-icon {
font-size: 24px;
color: #fff;
}
}
&.f-avatar-circle .f-avatar-icon {
border-radius: 100%;
}
&.f-avatar-square .f-avatar-icon {
border-radius: 0;
}
.f-avatar-upload-loading {
position: absolute;
left: 0;
top: 0;
display: inline-block;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.15);
.loading-inner {
position: absolute;
width: 100%;
left: 0;
top: 50%;
margin-top: -25px;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 16px;
color: #fff;
}
}
&:hover {
.f-avatar-icon {
display: flex;
}
}
}

View File

@ -3,9 +3,11 @@ import { ComputedRef } from 'vue';
export interface UseImage {
acceptTypes: ComputedRef<string>;
imageSrc: ComputedRef<string>;
imageSource: ComputedRef<string>;
imageTitle: ComputedRef<string>;
onClickImage: () => void;
}
export interface ImageFile {

View File

@ -1,10 +1,15 @@
import { computed, SetupContext } from 'vue';
import { vue } from '@vitejs/plugin-vue';
import { computed, ref, Ref, SetupContext } from 'vue';
import { AvatarProps } from '../avatar.props';
import { UseImage } from './types';
export function useImage(props: AvatarProps, context: SetupContext, fileInput: HTMLInputElement): UseImage {
const defaultImage = '';
const errorImage = '';
export function useImage(props: AvatarProps, context: SetupContext, fileInput: any, modelValue: Ref<string>): UseImage {
const defaultImage =
'data:image/jpeg;base64,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';
const errorImage =
'data:image/jpeg;base64,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';
const readonly = ref(props.readonly);
// 判断是否是图片路径
function isUrl(url: string) {
@ -39,33 +44,28 @@ export function useImage(props: AvatarProps, context: SetupContext, fileInput: H
return imageTypesArray.join(',');
});
const imageSrc = computed(() => {
if (!props.cover) {
return defaultImage;
const imageSource = computed(() => {
const image = modelValue.value || props.cover || defaultImage;
if (isUrl(image) || isBase64Image(image)) {
return image;
}
if (isUrl(props.cover)) {
return props.cover;
}
if (isBase64Image(props.cover)) {
return props.cover;
}
return appendBase64ImageHeader(props.cover);
return appendBase64ImageHeader(image);
});
const imageTitle = computed(() => {
return props.readonly ? '' : props.tile;
return readonly.value ? '' : props.tile;
});
function onClickImage() {
if (this.readonly) {
if (readonly.value) {
return;
}
fileInput.click();
fileInput && fileInput.value && fileInput.value.click();
}
function getImageFile() {
return this.imgFileObj;
}
return { acceptTypes, imageSrc, imageTitle };
return { acceptTypes, imageSource, imageTitle, onClickImage };
}

View File

@ -3,103 +3,104 @@ import { ButtonEditProps } from '../button-edit.props';
import { UseTextBox } from './types';
export function useTextBox(props: ButtonEditProps, context: SetupContext, modelValue: Ref<string>, displayText: Ref<string>): UseTextBox {
const textBoxTitle = computed(() => (props.enableTitle ? modelValue.value : ''));
const textBoxTitle = computed(() => (props.enableTitle ? modelValue.value : ''));
const textBoxPlaceholder = computed(() => ((props.disable || props.readonly) && !props.forcePlaceholder ? '' : props.placeholder));
const textBoxPlaceholder = computed(() => ((props.disable || props.readonly) && !props.forcePlaceholder ? '' : props.placeholder));
const isTextBoxReadonly = computed(() => props.readonly || !props.editable);
const isTextBoxReadonly = computed(() => props.readonly || !props.editable);
let focusState = false;
let focusState = false;
const hasFocusedTextBox = computed(() => focusState);
const hasFocusedTextBox = computed(() => focusState);
const textBoxClass = computed(() => ({
'text-left': props.textAlign === 'left',
'text-center': props.textAlign === 'center',
'text-right': props.textAlign === 'right',
'form-control': true,
'f-utils-fill': true,
}));
const textBoxClass = computed(() => ({
'text-left': props.textAlign === 'left',
'text-center': props.textAlign === 'center',
'text-right': props.textAlign === 'right',
'form-control': true,
'f-utils-fill': true
}));
function changeTextBoxValue(newValue: string, showEmitChangeEmit = true) {
if (modelValue.value !== newValue) {
modelValue.value = newValue;
if (showEmitChangeEmit) {
context.emit('change', newValue);
}
function changeTextBoxValue(newValue: string, showEmitChangeEmit = true) {
if (modelValue.value !== newValue) {
modelValue.value = newValue;
if (showEmitChangeEmit) {
context.emit('change', newValue);
}
context.emit('update:modelValue', newValue);
}
}
}
watch(
() => props.modelValue,
(value: string) => context.emit('change', value)
);
watch(
() => props.modelValue,
(value: string) => context.emit('change', value)
);
function onBlurTextBox($event: Event) {
focusState = false;
context.emit('blur', $event);
$event.stopPropagation();
}
function onClickTextBox($event: Event) {
context.emit('click', $event);
}
function onFocusTextBox($event: Event) {
if (props.disable) {
return;
function onBlurTextBox($event: Event) {
focusState = false;
context.emit('blur', $event);
$event.stopPropagation();
}
focusState = true;
if (!isTextBoxReadonly.value) {
context.emit('focus', $event);
function onClickTextBox($event: Event) {
context.emit('click', $event);
}
}
function onInput($event: Event) {
context.emit('input', ($event.target as HTMLInputElement).value);
const newValue = ($event.target as HTMLInputElement).value;
displayText.value = newValue;
if (modelValue.value !== newValue) {
changeTextBoxValue(newValue, false);
context.emit('update:modelValue', ($event.target as HTMLInputElement).value);
function onFocusTextBox($event: Event) {
if (props.disable) {
return;
}
focusState = true;
if (!isTextBoxReadonly.value) {
context.emit('focus', $event);
}
}
}
function onMouseDownTextBox($event: MouseEvent) {
const target = $event.target as HTMLElement;
if (target.tagName !== 'INPUT') {
$event.preventDefault();
function onInput($event: Event) {
context.emit('input', ($event.target as HTMLInputElement).value);
const newValue = ($event.target as HTMLInputElement).value;
displayText.value = newValue;
if (modelValue.value !== newValue) {
changeTextBoxValue(newValue, false);
// context.emit('update:modelValue', ($event.target as HTMLInputElement).value);
}
}
$event.stopPropagation();
}
function onKeyDownTextBox($event: Event) {
context.emit('keydown', $event);
}
function onMouseDownTextBox($event: MouseEvent) {
const target = $event.target as HTMLElement;
if (target.tagName !== 'INPUT') {
$event.preventDefault();
}
$event.stopPropagation();
}
function onKeyUpTextBox($event: Event) {
context.emit('keyup', $event);
}
function onKeyDownTextBox($event: Event) {
context.emit('keydown', $event);
}
function onTextBoxValueChange($event: Event) {
const newValue = ($event.target as HTMLInputElement).value;
changeTextBoxValue(newValue);
}
function onKeyUpTextBox($event: Event) {
context.emit('keyup', $event);
}
return {
hasFocusedTextBox,
isTextBoxReadonly,
textBoxClass,
textBoxPlaceholder,
textBoxTitle,
changeTextBoxValue,
onBlurTextBox,
onClickTextBox,
onFocusTextBox,
onInput,
onKeyDownTextBox,
onKeyUpTextBox,
onMouseDownTextBox,
onTextBoxValueChange,
};
function onTextBoxValueChange($event: Event) {
const newValue = ($event.target as HTMLInputElement).value;
changeTextBoxValue(newValue);
}
return {
hasFocusedTextBox,
isTextBoxReadonly,
textBoxClass,
textBoxPlaceholder,
textBoxTitle,
changeTextBoxValue,
onBlurTextBox,
onClickTextBox,
onFocusTextBox,
onInput,
onKeyDownTextBox,
onKeyUpTextBox,
onMouseDownTextBox,
onTextBoxValueChange
};
}

View File

@ -0,0 +1,15 @@
import type { App } from 'vue';
import FButton from './src/button.component';
import FButtonGroup from './src/button-group.component';
export * from './src/button.props';
export * from './src/button-group.props';
export { FButton };
export default {
install(app: App): void {
app.component(FButton.name, FButton);
},
};

View File

@ -0,0 +1,37 @@
import { defineComponent, computed } from 'vue';
import type { SetupContext } from 'vue';
import { buttonGroupProps, ButtonGroupProps } from './button-group.props';
import { useButtonGroup } from './composition/use-button-group';
export default defineComponent({
name: 'FButtonGroup',
props: buttonGroupProps,
emits: ['click', 'changeState', 'change', 'clickMenuOut'],
setup(props: ButtonGroupProps, context: SetupContext) {
// const { onClickButton } = useButtonGroup(props, context);
const fButtonGroupSize = computed(() => ({
'btn-group-lg': props.size === 'large',
'btn-group-sm': props.size === 'small'
}));
// 样式:
// 'btn '+
// (btn.type?'btn-'+ btn.type:'btn-link')+
// ' '+
// (btn.type && btn.type !== 'link' ? 'f-btn-ml' :'')
// btn btn-btn.type f-btn-ml
// btn btn-link
// const fButtonType = computed(() => ({
// 'btn-primary': props.buttonType === 'primary',
// 'btn-warning': props.buttonType === 'warning',
// 'btn-danger': props.buttonType === 'danger',
// 'btn-success': props.buttonType === 'success',
// 'btn-link': props.buttonType === 'link',
// 'btn-secondary': props.buttonType === 'secondary',
// }));
const theFlatButtons = useButtonGroup(props, context);
return () => '';
}
});

View File

@ -0,0 +1,63 @@
import { ExtractPropTypes, PropType } from 'vue';
// eslint-disable-next-line max-len
type PlacementDirection = 'top' | 'top-left' | 'top-right' | 'left' | 'left-top' | 'left-bottom' | 'bottom' | 'bottom-left' | 'bottom-right' | 'right' | 'right-top' | 'right-bottom';
export const buttonGroupProps = {
/**
*
*/
id: String,
/**
*
*/
rectifyReferenceH: { type: HTMLElement, default: 'referenceEl' },
/**
*
*/
rectifyReferenceV: { type: HTMLElement, default: 'referenceEl' },
/**
*
*/
autoRectify: { type: Boolean, default: 'true' },
/**
* placement
*/
realPlacement: { type: String, default: 'bottom-right' },
/**
* placement
*/
rectifyPlacement: { type: String, default: 'bottom-right' },
/**
*
*/
data: { type: Array },
/**
* 2
*/
count: { type: Number, default: 2 },
/**
*
*/
size: { type: String, default: 'small' },
/**
*
*/
type: { type: String, default: 'primary' },
/**
*
*/
placement: { type: String, default: 'bottom' }, /**
/**
*
*/
showPanel: { type: Boolean, default: false },
/**
*
*/
dpFlag: { type: Boolean, default: false },
};
export default buttonGroupProps;
export type ButtonGroupProps = ExtractPropTypes<typeof buttonGroupProps>;

View File

@ -0,0 +1,102 @@
import { defineComponent, computed } from 'vue';
import type { SetupContext } from 'vue';
import { buttonProps, ButtonProps } from './button.props';
import { useButton } from './composition/use-button';
export default defineComponent({
name: 'FButton',
props: buttonProps,
emits: ['click'],
setup(props: ButtonProps, context: SetupContext) {
const { onClickButton } = useButton(props, context);
const fButtonSize = computed(() => ({
'btn-lg': props.size === 'large',
'btn-sm': props.size === 'small',
}));
const fButtonType = computed(() => ({
'btn-primary': props.buttonType === 'primary',
'btn-warning': props.buttonType === 'warning',
'btn-danger': props.buttonType === 'danger',
'btn-success': props.buttonType === 'success',
'btn-link': props.buttonType === 'link',
'btn-secondary': props.buttonType === 'secondary',
}));
return () => (
// btn-lg btn btn-primary
// btn-sm btn btn-warning
<div>
<div style={'text-align:left;margin-top:10px;'}>
<div style={'margin-top:10px;'}>primary</div>
<button
class={[fButtonSize.value, ' btn btn-primary']}
style={'margin:5px'}
id={props.id}
disabled={props.disable}
onClick={onClickButton}>
</button>
<button
class={[fButtonSize.value, ' btn btn-danger']}
style={'margin:5px'}
id={props.id}
disabled={props.disable}
onClick={onClickButton}>
</button>
<button
class={[fButtonSize.value, ' btn btn-success']}
style={'margin:5px'}
id={props.id}
disabled={props.disable}
onClick={onClickButton}>
</button>
<button
class={[fButtonSize.value, ' btn btn-warning']}
style={'margin:5px'}
id={props.id}
disabled={props.disable}
onClick={onClickButton}>
</button>
<button
class={[fButtonSize.value, ' btn btn-secondary']}
style={'margin:5px'}
id={props.id}
disabled={props.disable}
onClick={onClickButton}>
</button>
<button
class={[fButtonSize.value, ' btn btn-link']}
style={'margin:5px'}
id={props.id}
disabled={props.disable}
onClick={onClickButton}>
</button>
</div>
<div style={'text-align:left'}>
<div style={'margin-top:10px;'}>size</div>
<button
class={['btn-sm btn', fButtonType.value]}
style={'margin:5px'}
id={props.id}
disabled={props.disable}
onClick={onClickButton}>
</button>
<button
class={['btn-lg btn', fButtonType.value]}
style={'margin:5px'}
id={props.id}
disabled={props.disable}
onClick={onClickButton}>
</button>
</div>
</div>
);
},
});

View File

@ -0,0 +1,26 @@
import { ExtractPropTypes, PropType } from 'vue';
type ButtonType = 'primary' | 'warning' | 'danger' | 'success' | 'link' | 'secondary';
type SizeType = 'small' | 'large';
export const buttonProps = {
/**
*
*/
id: String,
/**
*
*/
buttonType: { type: String as PropType<ButtonType>, default: 'primary' },
/**
*
*/
disable: { type: Boolean, default: false },
/**
*
*/
size: { type: String as PropType<SizeType>, default: 'small' },
// 待确定text参数
};
export type ButtonProps = ExtractPropTypes<typeof buttonProps>;

View File

@ -0,0 +1,13 @@
import { ComputedRef } from 'vue';
export interface UseButtonGroup {
/**
* Class
*/
// buttonClass: ComputedRef<Record<string, boolean | undefined>>;
/**
*
*/
clickEvent: ($event: Event) => void;
}

View File

@ -0,0 +1,13 @@
import { ComputedRef } from 'vue';
export interface UseButton {
/**
* Class
*/
// buttonClass: ComputedRef<Record<string, boolean | undefined>>;
/**
*
*/
onClickButton: ($event: Event) => void;
}

View File

@ -0,0 +1,329 @@
import { UseButtonGroup } from './types-group';
import { ButtonGroupProps } from '../button-group.props';
import { computed, SetupContext } from 'vue';
export function useButtonGroup(props: ButtonGroupProps, context: SetupContext): UseButtonGroup {
// ngAfterViewChecked() {
// if (this.show) {
// this.setPosition(this.event)
// }
// }
// function onClickButton($event: Event) {
// $event.stopPropagation();
// // this.disabled
// if (props.disable) {
// context.emit('clickButton', $event);
// }
// }
function clickEvent($event: Event) {
$event.stopPropagation();
props.showPanel = !props.showPanel;
// body添加面板
if (props.showPanel) {
this.appendBody();
this.event = $event;
// 绑定相应的事件
this.ngZone.runOutsideAngular(() => {
this.bindMenuMouseenter();
this.bindiMenuMouseleave();
this.mouseNotEnterLeave();
});
}
// 面板显示 脏值检测
this.changeRef.detectChanges();
this.changeRef.markForCheck();
context.emit('changeState', props.showPanel);
}
/* 按钮触发事件 */
function toggle($event: any, btn) {
$event.stopPropagation();
if (btn.disabled) return;
props.showPanel = false;
// 关闭下拉按钮面板 脏值检测
this.changeRef.detectChanges();
this.changeRef.markForCheck();
context.emit('change', btn.id);
context.emit('click', btn);
}
/* 显示出来的按钮组 */
function flatButtons() {
return props.data && props.data.slice(0, this.count);
}
function dpButtons() {
return props.data && props.data.slice(this.count);
}
// 下拉按钮显示到body中 可改变面板方向
function appendBody() {
if (this.dpMenu.nativeElement) {
// 添加到body 便于全部显示
document.body.appendChild(this.dpMenu.nativeElement);
}
}
/**
* ,
*
* @param btnSize
*/
function changePlacement(btnSize: any) {
if (!props.autoRectify) {
return;
}
const referPosition = this.getReferencePosition();
let newPlacement = props.realPlacement;
if (newPlacement.indexOf('bottom') > -1) {
if (this._menuHeight > referPosition.bottom - btnSize.bottom) {
newPlacement = newPlacement.replace('bottom', 'top');
}
} else if (newPlacement.indexOf('top') > -1) {
if (this._menuHeight > btnSize.top - referPosition.top) {
newPlacement = newPlacement.replace('top', 'bottom');
}
}
if (newPlacement.indexOf('left') > -1) {
if (this._menuWidth > btnSize.left - referPosition.left) {
newPlacement = newPlacement.replace('left', 'right');
}
} else if (newPlacement.indexOf('right') > -1) {
if (this._menuWidth > referPosition.right - btnSize.right) {
newPlacement = newPlacement.replace('right', 'left');
}
}
this.rectifyPlacement = newPlacement;
}
/**
*
*/
function getReferencePosition() {
let rRight = document.documentElement.clientWidth;
let rBottom = document.documentElement.clientHeight;
let rTop = 0;
let rLeft = 0;
// 横向参照
if (props.rectifyReferenceH) {
rRight = props.rectifyReferenceH.getBoundingClientRect().right;
rLeft = props.rectifyReferenceH.getBoundingClientRect().left;
}
// 纵向参照
if (props.rectifyReferenceV) {
rBottom = props.rectifyReferenceV.getBoundingClientRect().bottom;
rTop = props.rectifyReferenceV.getBoundingClientRect().top;
}
return { top: rTop, left: rLeft, right: rRight, bottom: rBottom };
}
/**
* class
* @param position
*/
function _getClsName(position) {
let className = '';
switch (position) {
case 'top-right':
case 'top':
// 朝上,朝上-朝右
className = 'dropup';
break;
case 'top-left':
// 朝上-朝左
className = 'dropup-left';
break;
case 'left-bottom':
case 'left':
// 横向——朝左——朝下
className = 'dropleft';
break;
case 'left-top':
// 横向——朝左——朝上
className = 'dropleft-up';
break;
case 'right-bottom':
case 'right':
// 横向——朝右——朝下
className = 'dropright';
break;
case 'right-top':
// 横向——朝右——朝上
className = 'dropright-up';
break;
case 'bottom-left':
// 朝下——朝左
className = 'dropdown-left';
break;
case 'bottom-right':
className = 'dropdown';
break;
default:
// 朝下,朝下——朝右
className = 'dropdown';
}
return className;
}
function getRealPlacement(pment) {
let result = 'bottom-right';
switch (pment) {
case 'top':
result = 'top-right';
break;
case 'left':
result = 'left-bottom';
break;
case 'right':
result = 'right-bottom';
break;
case 'bottom':
result = 'bottom-right';
break;
default:
result = pment;
}
return result;
}
/*
*
*/
function changePosition(btnSize: any) {
let rplacement = '';
if (props.autoRectify) {
rplacement = this.rectifyPlacement;
} else {
rplacement = props.realPlacement;
}
let styleTop = 0;
let styleLeft = 0;
if (rplacement.indexOf('top') > -1) {
styleTop = btnSize.top - this._menuHeight;
} else if (rplacement.indexOf('bottom') > -1) {
styleTop = btnSize.bottom;
}
if (rplacement.indexOf('right') > -1) {
styleLeft = btnSize.right;
} else if (rplacement.indexOf('left') > -1) {
styleLeft = btnSize.left - this._menuWidth;
}
// 开头
if (rplacement.indexOf('-top') > -1) {
styleTop -= btnSize.height;
} else if (rplacement.indexOf('-bottom') > -1) {
styleTop += btnSize.height;
}
this.dpMenu.nativeElement.style.top = styleTop + 'px';
this.dpMenu.nativeElement.style.left = styleLeft + 'px';
}
/* 绑定下拉面板鼠标进入事件 */
function bindMenuMouseenter() {
this.mouseenterEvent = this.changeFlagToTrue.bind(this);
this.dpMenu.nativeElement.addEventListener('mouseenter', this.mouseenterEvent);
}
/* 绑定下拉面板鼠标离开事件 */
function bindiMenuMouseleave() {
this.mouseleaveEvent = this.mouseLeave.bind(this);
this.dpMenu.nativeElement.addEventListener('mouseleave', this.mouseleaveEvent);
}
/* 绑定点击面板区域之外触发的事件 */
// bindDocClick() {
// this.documentClickEvent = this.clickDoc.bind(this);
// document.addEventListener('click', this.documentClickEvent);
// }
/* 解绑事件 */
function unbindMenuMouseenter() {
if (this.mouseenterEvent) {
this.dpMenu.nativeElement.removeEventListener('mouseenter', this.mouseenterEvent);
}
}
function unbindiMenuMouseleave() {
if (this.mouseleaveEvent) {
this.dpMenu.nativeElement.removeEventListener('mouseleave', this.mouseleaveEvent);
}
}
// unbindDocClick() {
// if (this.documentClickEvent) {
// document.removeEventListener('click', this.documentClickEvent);
// }
// }
/* flag true */
function changeFlagToTrue() {
props.dpFlag = true;
}
/* flag false */
function changeFlagToFalse() {
props.dpFlag = false;
}
/* 鼠标离开时 关闭menu */
function mouseLeave() {
if (props.dpFlag) {
this.changeFlagToFalse();
// this.unbindDocClick();
this.unbindiMenuMouseleave();
this.unbindMenuMouseenter();
this.close();
if (this.setTimeObj) {
this.ngZone.runOutsideAngular(() => {
clearTimeout(this.setTimeObj);
});
}
}
}
/* 鼠标没有进入到面板 一段时间后面板自动消失 */
function mouseNotEnterLeave() {
this.ngZone.runOutsideAngular(() => {
this.setTimeObj = setTimeout(() => {
if (!props.dpFlag) {
this.changeFlagToFalse();
// this.unbindDocClick();
this.unbindiMenuMouseleave();
this.unbindMenuMouseenter();
this.close();
if (this.setTimeObj) {
clearTimeout(this.setTimeObj);
}
}
}, 2000);
});
}
/* 关闭下拉面板 */
function close() {
props.showPanel = false;
// 关闭下拉按钮面板 脏值检测
if (!this.changeRef.destroyed) {
this.changeRef.detectChanges();
this.changeRef.markForCheck();
}
this.dpBtn.nativeElement.blur();
}
/* 动态指定menu在body中的位置 */
function setPosition(e) {
// 下拉按钮
const btnSize = this.dpBtn.nativeElement.getBoundingClientRect();
// 下拉面板
const menuRect = this.dpMenu.nativeElement.getBoundingClientRect();
this._menuHeight = menuRect.height;
this._menuWidth = menuRect.width;
// 如果要自动纠正方向
if (props.autoRectify) {
this.changePlacement(btnSize);
}
this.changePosition(btnSize);
}
return {
clickEvent
};
}

View File

@ -0,0 +1,24 @@
import { UseButton } from './types';
import { ButtonProps } from '../button.props';
import { computed, SetupContext } from 'vue';
export function useButton(props: ButtonProps, context: SetupContext): UseButton {
// const buttonClass = computed(() => ({
// // 'input-group-append': true,
// // 'append-force-show': props.showButtonWhenDisabled && (props.readonly || props.disable),
// }));
function onClickButton($event: Event) {
$event.stopPropagation();
// this.disabled
if (props.disable) {
context.emit('clickButton', $event);
}
}
return {
// buttonClass,
onClickButton
};
}

View File

@ -19,7 +19,7 @@ export default defineComponent({
const toastClass = computed(() => {
const classObject = {
animated: showingToast.value,
toast: true,
toast: true
};
classObject[props.animate] = false;
classObject[animateEnd] = showingToast.value;
@ -93,7 +93,7 @@ export default defineComponent({
return () => {
return (
<div class={toastClass}>
<div class={toastClass.value}>
{shouldShowCloseButton.value && (
<button class="toast-close f-btn-icon f-bare" onClick={onCloseToast}>
<span class="f-icon modal_close"></span>
@ -127,5 +127,5 @@ export default defineComponent({
</div>
);
};
},
}
});

View File

@ -3,7 +3,7 @@ import { NotifyData, ToastyAnimate } from '../notify.props';
export const toastProps = {
animate: { type: String as PropType<ToastyAnimate>, default: 'fadeIn' },
options: { type: Object as PropType<NotifyData> },
options: { type: Object as PropType<NotifyData> }
};
export type ToastProps = ExtractPropTypes<typeof toastProps>;

View File

@ -1,21 +1,22 @@
import { isFunction } from 'lodash';
import { computed, defineComponent, ref, SetupContext } from 'vue';
import { NotifyData, NotifyProps, notifyProps } from './notify.props';
import Toast from './components/toast.component';
export default defineComponent({
name: 'Notify',
props: notifyProps,
emits: ['empty'],
emits: ['close', 'empty'],
setup(props: NotifyProps, context: SetupContext) {
const notifyClass = computed(() => ({
'farris-notify': true,
'farris-notify': true
}));
const defaultNotifyDistance = {
left: 12,
right: 12,
top: 136,
bottom: 12,
bottom: 12
};
const toasts = ref(props.toasts || []);
@ -24,10 +25,12 @@ export default defineComponent({
left: props.position.indexOf('left') > -1 ? `${props.left ? props.left : defaultNotifyDistance.left}px` : '',
right: props.position.indexOf('right') > -1 ? `${props.right ? props.right : defaultNotifyDistance.right}px` : '',
top: props.position.indexOf('top') > -1 ? `${props.top ? props.top : defaultNotifyDistance.top}px` : '',
bottom: props.position.indexOf('bottom') > -1 ? `${props.bottom ? props.bottom : defaultNotifyDistance.bottom}px` : '',
bottom: props.position.indexOf('bottom') > -1 ? `${props.bottom ? props.bottom : defaultNotifyDistance.bottom}px` : ''
}));
function closeToast(toast: NotifyData) {}
function closeToast(toast: NotifyData) {
context.emit('close');
}
function addToast(toast: NotifyData) {
if (toasts.value.length >= props.limit) {
@ -71,16 +74,10 @@ export default defineComponent({
return (
<div id={props.id} class={notifyClass.value} style={notifyStyle.value}>
{toasts.value.map((toastData: NotifyData) => {
return (
<f-toast
v-model={toastData}
animate={props.animate}
onClose={($event: Event) => onClose($event, toastData)}
onClick={onClick}></f-toast>
);
return <Toast options={toastData} animate={props.animate} onClose={($event) => onClose($event, toastData)}></Toast>;
})}
</div>
);
};
},
}
});

View File

@ -50,6 +50,6 @@ export const notifyProps = {
id: { type: String },
animate: { type: String as PropType<ToastyAnimate>, default: 'fadeIn' },
toasts: { type: Array<NotifyData> },
options: { type: Object as PropType<NotifyData> },
options: { type: Object as PropType<NotifyData> }
};
export type NotifyProps = ExtractPropTypes<typeof notifyProps>;

View File

@ -0,0 +1,29 @@
import { reactive, createApp, onUnmounted } from 'vue';
import type { App } from 'vue';
import { NotifyProps } from './notify.props';
import Notify from './notify.component';
function initInstance(props: NotifyProps, content?: string): App {
const container = document.createElement('div');
container.style.display = 'contents';
const app: App = createApp({
setup() {
onUnmounted(() => {
document.body.removeChild(container);
});
return () => <Notify {...props} onClose={app.unmount}></Notify>;
}
});
document.body.appendChild(container);
app.mount(container);
return app;
}
export default class NotifyService {
static show(options: NotifyProps): void {
const props: NotifyProps = reactive({
...options
});
initInstance(props);
}
}

View File

@ -1,26 +1,46 @@
import { computed, defineComponent, SetupContext } from 'vue';
import { computed, defineComponent, ref, SetupContext } from 'vue';
import { PopoverProps, popoverProps } from './popover.props';
import './popover.scss';
export default defineComponent({
name: 'FPopover',
props: popoverProps,
emits: [],
setup(props: PopoverProps, context: SetupContext) {
const position = ref(props.position);
const shouldShowTitle = computed(() => !!props.title);
const popoverClass = computed(()=>{
const originPopover = `popover in popover-${position.value}`;
const bsPopover = `bs-popover-${position.value}`
const popoverClassObject = {
'popover in pop'
};
'[class]':
'"popover in popover-" + placement + " " + "bs-popover-" + placement + " " + placement + " " + containerClass',
'[class.show]': '!isBs3',
'[class.bs3]': 'isBs3',
role: 'tooltip',
style: 'display:block;'
})
const popoverContainerClass = computed(() => ({
'popover-content': true,
'popover-body': true,
'popover-body': true
}));
return () => {
return (
<>
<div>
<div class="popover-arrow arrow"></div>
{shouldShowTitle.value && <h3 class="popover-title popover-header">{props.title}</h3>}
<div class={popoverContainerClass.value}>{context.slots.default && context.slots?.default()}</div>
</>
</div>
);
};
},
}
});

View File

@ -4,7 +4,7 @@ export type PopoverPosition = 'top' | 'bottom' | 'left' | 'right' | 'auto';
export const popoverProps = {
title: { type: String },
position: { type: String as PropType<PopoverPosition>, default: 'top' },
position: { type: String as PropType<PopoverPosition>, default: 'top' }
};
export type PopoverProps = ExtractPropTypes<typeof popoverProps>;

View File

@ -0,0 +1,22 @@
.bs3.popover-top {
margin-bottom: 10px;
}
.bs3.popover.top > .arrow {
margin-left: -2px;
}
.bs3.popover.top {
margin-bottom: 10px;
}
.popover.bottom > .arrow {
margin-left: -4px;
}
.bs3.bs-popover-left {
margin-right: 0.5rem;
}
.bs3.bs-popover-right .arrow,
.bs3.bs-popover-left .arrow {
margin: 0.3rem 0;
}
.arrow-left .arrow {
left: calc(50% - 10px);
}

View File

@ -0,0 +1,12 @@
import type { App } from 'vue';
import RadioGroup from './src/radio-group.component';
export * from './src/radio-group.props';
export { RadioGroup };
export default {
install(app: App): void {
app.component(RadioGroup.name, RadioGroup);
}
};

View File

@ -0,0 +1,38 @@
import { ChangeRadio, Radio } from './types';
import { computed, Ref, SetupContext } from 'vue';
import { RadioGroupProps } from '../radio-group.props';
export function changeRadio(props: RadioGroupProps, context: SetupContext, modelValue: Ref<string>): ChangeRadio {
const canChangeRadioButton = computed(() => !props.disabled);
const enumData = computed(() => props.enumData || []);
function getValue(item: Radio): any {
return item[props.valueField];
};
function getText(item: Radio): any {
return item[props.textField];
};
function onClickRadio(item, $event: Event) {
if (canChangeRadioButton.value) {
const newValue = getValue(item);
if (modelValue.value !== item) {
modelValue.value = newValue;
context.emit('changeValue', newValue);
context.emit('update:modelValue', newValue);
}
}
$event.stopPropagation();
}
return {
enumData,
getValue,
getText,
onClickRadio
};
}

View File

@ -0,0 +1,32 @@
import { ComputedRef, Ref } from 'vue';
export interface Radio {
/**
*
*/
value: ComputedRef<any>;
/**
*
*/
name: ComputedRef<any>;
}
export interface ChangeRadio {
enumData: ComputedRef<Array<Radio>>;
/**
*
*/
getValue(item: Radio): any;
/**
*
*/
getText(item: Radio): any;
/**
*
*/
onClickRadio: (item: Radio, $event: Event) => void;
}

View File

@ -0,0 +1,56 @@
import { defineComponent, computed, ref } from 'vue';
import type { SetupContext } from 'vue';
import { radioGroupProps, RadioGroupProps } from './radio-group.props';
import { changeRadio } from './composition/change-radio';
export default defineComponent({
name: 'FRadioGroup',
props: radioGroupProps,
emits: [
'changeValue',
'update:modelValue',
],
setup(props: RadioGroupProps, context: SetupContext) {
const modelValue = ref(props.modelValue);
const { enumData, onClickRadio, getValue, getText } = changeRadio(props, context, modelValue);
const horizontalClass = computed(() => ({
'farris-checkradio-hor': props.horizontal
}));
return () => {
return (
<div class={['farris-input-wrap', horizontalClass.value]}>
{
enumData.value.map((item, index) => {
const id = 'radio_' + props.name + index;
return (
<div class="custom-control custom-radio" >
<input
type="radio"
class="custom-control-input"
name={props.name}
id={id}
value={getValue(item)}
checked={getValue(item) === modelValue.value}
disabled={props.disabled}
tabindex={props.tabIndex}
onClick={(event: MouseEvent) => onClickRadio(item, event)}
/>
<label class="custom-control-label" for={id}>
{ getText(item) }
</label>
</div>
);
})
}
</div>
);
};
},
});

View File

@ -0,0 +1,45 @@
import { ExtractPropTypes, PropType } from 'vue';
import { Radio } from './composition/types';
export const radioGroupProps = {
/**
*
*/
id: String,
/**
*
*/
name: { type: String, default: '' },
/**
*
*/
enumData: Array<Radio>,
/**
* key值
*/
textField: { type: String, default: 'name' },
/**
* key值
*/
valueField: { type: String, default: 'value' },
/**
*
*/
horizontal: { type: Boolean, default: false },
/**
*
*/
disabled: { type: Boolean, default: false },
/**
*
*/
modelValue: { type: String, default: '' },
/**
* Tab键索引
*/
tabIndex: Number,
};
export type RadioGroupProps = ExtractPropTypes<typeof radioGroupProps>;

View File

@ -10,6 +10,14 @@ export default defineComponent({
return true;
});
const shouldShowHeaderTitle = computed(() => {
return true;
});
const shouldShowSubHeaderTitle = computed(() => {
return true;
});
const shouldShowToolbarInHeader = computed(() => {
return false;
});
@ -28,65 +36,68 @@ export default defineComponent({
const toolbarButtons = ref([]);
const headerClass = computed(() => ({}));
const headerClass = computed(() => {
const customClassArray = props.headerClass.split(' ');
const headClassObject = {
'f-section-header': true
};
customClassArray.reduce<Record<string, unknown>>((classObject, classString) => {
classObject[classString] = true;
return classObject;
}, headClassObject);
return headClassObject;
});
const extendAreaClass = computed(() => ({}));
const contentClass = computed(() => ({
'f-section-content': true,
const extendAreaClass = computed(() => ({
'f-section-extend': true
}));
function getToolbarState() {
const contentClass = computed(() => {
const customClassArray = props.contentClass.split(' ');
const contentClassObject = {
'f-section-content': true
};
customClassArray.reduce<any>((classObject, classString) => {
classObject[classString] = true;
return classObject;
}, contentClassObject);
return contentClassObject;
});
function getToolbarState(buttonId: string, visiableMap: any, defaultValue: boolean) {
return true;
}
const mainTitle = ref(props.mainTitle);
const subTitle = ref(props.subTitle);
function renderSectionHeader() {
return (
shouldShowHeader.value && (
<div class={headerClass.value}>
{shouldShowHeaderTitle.value && (
<div class="f-title">
<h4 class="f-title-text">{mainTitle.value}</h4>
{shouldShowSubHeaderTitle.value && <span>{subTitle}</span>}
</div>
)}
</div>
)
);
}
function renderSectionContent() {
return <div class={contentClass.value}>{context.slots.default && context.slots.default()}</div>;
}
return () => {
return (
<>
{shouldShowHeader.value && (
<div class="f-section-header" class={headerClass.value}>
{shouldShowToolbarInHeader.value && (
<div class="f-section-header--btn-placeholder">
{toolbarButtons.value
.filter((toolButton: ButtonConfig) => getToolbarState(toolButton.id, btnVisible, true))
.forEach((toolButton: ButtonConfig) => {
return <button class={toolButton.appearance.class}>{toolButton.title}</button>;
})}
</div>
)}
</div>
)}
{shouldShowToolbarInContent.value && (
<div class="f-section-header--btn-placeholder">
{toolbarButtons.value
.filter((toolButton: ButtonConfig) => getToolbarState(toolButton.id, btnVisible, true))
.forEach((toolButton: ButtonConfig) => {
return <button class={toolButton.appearance.class}>{toolButton.title}</button>;
})}
</div>
)}
{shouldShowExtendArea.value && (
<div class="f-section-extend" class={extendAreaClass.value}>
{context.slots.extend && context.slots.extend()}
</div>
)}
<div class={contentClass.value}>
{shouldShowToolbarTemplateInContent.value && (
<div class="f-section-toolbar f-section-content--toolbar">
{context.slots.toolbarButtons &&
context.slots.toolbarButtons({
datas: toolbarBtns,
dpHidden: toolbarDpHidden,
dpDatas: inMoreButtonContents,
})}
</div>
)}
{context.slots.default && context.slots.default()}
</div>
</>
<div class="f-section">
{renderSectionHeader()}
{renderSectionContent()}
</div>
);
};
},
}
});

View File

@ -1,11 +1,15 @@
import { ExtractPropTypes, PropType } from 'vue';
export interface ButtonAppearance {
class: string;
}
export interface ButtonConfig {
id: string;
disable: boolean;
title: string;
click: any;
appearance: object;
appearance: ButtonAppearance;
visible?: boolean;
}
@ -32,5 +36,6 @@ export const sectionProps = {
toolbar: { type: Object as PropType<ToolbarConfig>, default: {} },
showToolbarMoreButton: { type: Boolean, default: true },
clickThrottleTime: { type: Number, default: 350 },
headerClass: { type: String, default: '' }
};
export type SectionProps = ExtractPropTypes<typeof sectionProps>;

View File

@ -1,24 +1,15 @@
import { computed, defineComponent, ref, SetupContext } from 'vue';
import { switchProps, SwitchProps, SwitchType } from './switch.props';
import { computed, defineComponent, onMounted, ref, SetupContext, toRefs, watch } from 'vue';
import { switchProps, SwitchProps } from './switch.props';
export default defineComponent({
name: 'FSwitch',
props: switchProps,
emits: [],
emits: ['update:modelValue'],
setup(props: SwitchProps, context: SetupContext) {
const checked = ref(false);
const { disable, editable, square, size, checkedLabel, uncheckedLabel } = toRefs(props);
const disable = ref(false);
const editable = ref(false);
const squire = ref(false);
const size = ref(props.size);
const checkedLabel = ref('');
const uncheckedLabel = ref('');
const checked = ref(props.checked);
const modelValue = ref(props.modelValue);
function getColor(flag = '') {
if (flag === 'borderColor') {
@ -53,35 +44,64 @@ export default defineComponent({
'f-cmp-switch': true,
checked: checked.value,
disabled: disable.value || !editable.value,
squire: squire.value,
squire: square.value,
'switch-large': size.value === 'large',
'switch-medium': size.value === 'medium',
'switch-small': size.value === 'small',
'switch-small': size.value === 'small'
}));
const switchContainerStyle = computed(() => ({
outline: 'none',
'backgroud-color': getBackgroundColor(),
'border-color': getBorderColor(),
'border-color': getBorderColor()
}));
const smallStyle = computed(() => ({
background: getSwitchColor(),
background: getSwitchColor()
}));
const shouldShowSwitch = computed(() => {
// checkedLabel || uncheckedLabel
return checkedLabel.value || uncheckedLabel.value;
return checkedLabel?.value || uncheckedLabel?.value;
});
function updateChecked($event: any, emitClick = true) {
if (disable.value || !editable.value) {
return;
}
checked.value = !checked.value;
modelValue.value = checked.value;
context.emit('update:modelValue', checked.value);
}
function onToggle($event: MouseEvent) {
updateChecked($event);
}
watch(
() => props.modelValue,
(value: boolean) => {
checked.value = value;
}
);
onMounted(() => {
checked.value = props.modelValue;
});
return () => {
return (
<>
<span tabindex="0" role="button" class={switchContainerClass.value} style={switchContainerStyle.value}>
<span
tabindex="0"
role="button"
class={switchContainerClass.value}
style={switchContainerStyle.value}
onClick={onToggle}>
{shouldShowSwitch.value && (
<span class="switch-pane">
<span class="switch-label-checked">{checkedLabel.value}</span>
<span class="switch-label-unchecked">{uncheckedLabel.value}</span>
<span class="switch-label-checked">{checkedLabel?.value}</span>
<span class="switch-label-unchecked">{uncheckedLabel?.value}</span>
</span>
)}
@ -90,5 +110,5 @@ export default defineComponent({
</>
);
};
},
}
});

View File

@ -11,12 +11,16 @@ export const switchProps = {
defaultBorderColor: { type: String },
checkedLabel: { type: String },
uncheckedLabel: { type: String },
checked: { type: Boolean },
checked: { type: Boolean, default: false },
readonly: { type: Boolean },
disable: { type: Boolean },
editable: { type: Boolean, default: true },
reverse: { type: Boolean },
trueValue: { type: Object, default: true },
falseValue: { type: Object, default: false },
/**
*
*/
modelValue: { type: Boolean, default: false }
};
export type SwitchProps = ExtractPropTypes<typeof switchProps>;

View File

@ -30,7 +30,7 @@ export default defineComponent({
fill.value || tabType.value === 'fill';
});
const shouldShowNavPills = computed(() => {});
const shouldShowNavPills = computed(() => { });
const { setActiveId } = useTabs(props, context)
@ -39,11 +39,11 @@ export default defineComponent({
'farris-tabs-inHead': hasInHeadClass.value,
'farris-tabs-inContent': !hasInHeadClass.value,
'farris-tabs-nav-fill': shouldShowNavFill.value,
'farris-tabs-nav-pills': shouldShowNavPills.value,
'farris-tabs-nav-pills': shouldShowNavPills.value
}));
const tabsTitleStyle = computed(() => ({
width: hasInHeadClass.value ? (props.titleWidth ? `${props.titleWidth}%` : '') : '',
width: hasInHeadClass.value ? (props.titleWidth ? `${props.titleWidth}%` : '') : ''
}));
const tabsTitleButtonClass = computed(() => ({
@ -51,14 +51,14 @@ export default defineComponent({
'sc-nav-btn': true,
'px-1': true,
'sc-nav-lr': true,
'd-none': hideButtons.value,
'd-none': hideButtons.value
}));
const tabParentClass = computed(() => ({
spacer: true,
'f-utils-fill': true,
'spacer-sides': !hideButtons.value && hideDropDown.value,
'spacer-sides-dropdown': !hideButtons.value && !hideDropDown.value,
'spacer-sides-dropdown': !hideButtons.value && !hideDropDown.value
}));
const tabContainerClass = computed(() => ({
@ -68,9 +68,9 @@ export default defineComponent({
'nav-fill': fill.value || tabType.value === 'fill',
'nav-pills': tabType.value === 'pills',
'flex-row': position.value === 'top' || position.value === 'bottom',
'flex-column': position.value === 'left' || position.value === 'right',
'flex-column': position.value === 'left' || position.value === 'right'
}));
let tabPages = context.slots.default && context.slots.default()
tabPages?.forEach((tabPage: any) => {
@ -126,7 +126,7 @@ export default defineComponent({
return {
'st-tab-text': true,
'farris-title-auto': props.autoTitleWidth,
'farris-title-text-custom': tab.titleOverflow,
'farris-title-text-custom': tab.titleOverflow
};
}
@ -179,5 +179,5 @@ export default defineComponent({
</>
);
};
},
}
});

View File

@ -15,14 +15,14 @@ export default defineComponent({
const textClass = computed(() => ({
'f-form-control-text': !isTextArea.value,
'f-form-context-textarea': isTextArea,
'f-component-text-auto-size': autoSize.value,
'f-component-text-auto-size': autoSize.value
}));
const textStyle = computed(() => ({
textalign: textAlginment.value,
height: !autoSize.value && height.value > 0 ? `${height.value}px` : '',
'min-height': !autoSize.value && height.value > 0 ? `${height.value}px` : '',
'max-height': !autoSize.value && maxHeight.value > 0 ? `${maxHeight.value}px` : '',
'max-height': !autoSize.value && maxHeight.value > 0 ? `${maxHeight.value}px` : ''
}));
const text = computed(() => {
@ -37,5 +37,5 @@ export default defineComponent({
</span>
);
};
},
}
});

View File

@ -12,7 +12,7 @@ export default defineComponent({
const tooltipClass = computed(() => ({
tooltip: true,
show: true,
show: true
}));
const shouldShowTooltipText = computed(() => isTextContext.value);
@ -31,5 +31,5 @@ export default defineComponent({
</div>
);
};
},
}
});

View File

@ -18,6 +18,6 @@ export const tooltipProps = {
content: { type: String },
width: { type: Number },
customClass: { type: String },
position: { type: String as PropType<TooltipPosition>, default: 'top' },
position: { type: String as PropType<TooltipPosition>, default: 'top' }
};
export type TooltipProps = ExtractPropTypes<typeof tooltipProps>;

10789
packages/ui-vue/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +1,63 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import { ref } from "vue";
import { ref } from 'vue';
import HelloWorld from './components/hello-world.vue';
import ButtonEdit from "../components/button-edit/src/button-edit.component";
import Avatar from './components/avatar.vue';
import Tabs from './components/tabs.vue'
import ButtonEdit from './components/button-edit.vue';
import FButton from '../components/button/src/button.component';
import Switch from './components/switch.vue';
import RadioGroup from './components/radio-group.vue';
import Section from './components/section.vue';
import Notify from './components/notify.vue';
import Accordion from './components/accordion.vue';
const canEdit = ref(true);
const disable = ref(false);
const canAutoComplete = ref(false);
</script>
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<input type="checkbox" id="checkbox" v-model="canEdit" />
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
<!-- <input type="checkbox" id="checkbox" v-model="canEdit" />
<label for="checkbox">editable:{{ canEdit }}</label>
<input type="checkbox" id="checkbox" v-model="canAutoComplete" />
<label for="checkbox">auto complete:{{ canAutoComplete }}</label>
<HelloWorld msg="Vite + Vue" />
<ButtonEdit :editable="canEdit" :auto-complete="canAutoComplete" :enable-clear="true"></ButtonEdit>
<Avatar></Avatar>
<Tabs />
<label for="checkbox">auto complete:{{ canAutoComplete }}</label> -->
<!-- <ButtonEdit :editable="canEdit" :auto-complete="canAutoComplete" :enable-clear="true"></ButtonEdit> -->
<ButtonEdit></ButtonEdit>
<Avatar></Avatar>
<input type="checkbox" id="checkbox" v-model="disable" />
<label for="checkbox">disable:{{ disable }}</label>
<FButton :disable="disable"></FButton>
<Switch></Switch>
<RadioGroup></RadioGroup>
<Section></Section>
<Notify></Notify>
<Accordion></Accordion>
<Tabs />
</template>
<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
</style>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import According from '../../components/according/src/according.component';
import AccordingItem from '../../components/according/src/components/according-item.component';
</script>
<template>
<According>
<AccordingItem title="According Panel One"></AccordingItem>
<AccordingItem title="According Panel Two"></AccordingItem>
</According>
</template>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import Accordion from '../../components/accordion/src/accordion.component';
import AccordionItem from '../../components/accordion/src/components/accordion-item.component';
</script>
<template>
<Accordion>
<AccordionItem title="According Panel One"></AccordionItem>
<AccordionItem title="According Panel Two"></AccordionItem>
</Accordion>
</template>

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import { ref } from 'vue';
import ButtonEdit from '../../components/button-edit/src/button-edit.component';
const canEdit = ref(true);
const canAutoComplete = ref(false);
const text = ref('');
</script>
<template>
<input type="checkbox" id="checkbox" v-model="canEdit" />
<label for="checkbox">editable:{{ canEdit }}</label>
<input type="checkbox" id="checkbox" v-model="canAutoComplete" />
<label for="checkbox">auto complete:{{ canAutoComplete }}</label>
<ButtonEdit :editable="canEdit" :auto-complete="canAutoComplete" :enable-clear="true" v-model="text"></ButtonEdit>
Text: {{text}}
</template>

View File

@ -0,0 +1,25 @@
<script setup lang="ts">
import NotifyService from '../../components/notify/src/notify.service';
function showMessage() {
NotifyService.show({
limit: 5,
showCloseButton: false,
position: 'top-center',
timeout: 3000,
theme: 'bootstrap',
left: 0,
right: 0,
top: 0,
bottom: 0,
id: '1',
animate: 'fadeIn',
options: { type: 'string', title: 'string', msg: 'string' },
toasts: [{ type: 'string', title: 'string', msg: 'string' }]
});
}
</script>
<template>
<button class="btn btn-primary" @click="showMessage">show notify</button>
</template>

View File

@ -0,0 +1,39 @@
<script setup lang="ts">
import { ref } from "vue";
import RadioGroup from "../../components/radio-group/src/radio-group.component";
const radioDisabled = ref(false);
const radioEnumData = ref([
{ value: "aa", name: "标签一" },
{ value: "bb", name: "标签二" },
{ value: "cc", name: "标签三" },
]);
const radioHorizontal = ref(false);
const value = ref("aa");
function testConsole(value) {
// console.log("change radio value to", value);
}
</script>
<template>
<p></p>
<label for="radio_disabled" class="mr-1">是否禁用单选组</label>
<input type="checkbox" id="radio_disabled" v-model="radioDisabled" />
<label for="radio_horizontaol" class="mr-1 ml-3">是否水平排列</label>
<input type="checkbox" id="radio_horizontaol" v-model="radioHorizontal" />
<br />
<RadioGroup
:disabled="radioDisabled"
:enum-data="radioEnumData"
:horizontal="radioHorizontal"
v-model="value"
@change-value="testConsole"
>
</RadioGroup>
<br />
当前选中值: {{ value }}
</template>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
import Section from '../../components/section/src/section.component';
</script>
<template>
<Section show-header main-title="This is a section title">
<div>
<span>This is setion content.</span>
</div>
</Section>
</template>

View File

@ -0,0 +1,15 @@
<script setup lang="ts">
import { ref } from 'vue';
import Switch from '../../components/switch/src/switch.component';
const canEdit = ref(false);
const canAutoComplete = ref(false);
const text = ref('');
</script>
<template>
<input type="checkbox" id="checkbox" v-model="canEdit" />
<label for="checkbox">switch value : {{ canEdit }}</label>
<Switch v-model="canEdit"></Switch>
</template>

2852
yarn.lock

File diff suppressed because it is too large Load Diff