Implements style、size and disabled states of button
This commit is contained in:
commit
528b02d172
|
@ -0,0 +1,5 @@
|
|||
node_modules/*
|
||||
packages/**/node_modules/*
|
||||
packages/**/dist/*
|
||||
packages/**/build/*
|
||||
packages/**/lib/*
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"root": true,
|
||||
"extends": ["@farris"],
|
||||
"rules": {
|
||||
"prefer-object-spread": "off"
|
||||
}
|
||||
}
|
|
@ -1,53 +1,23 @@
|
|||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
**/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events.json
|
||||
speed-measure-plugin.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
*.lock
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
package-lock.json
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.prettierrc
|
||||
package-lock.json
|
||||
debug.log
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install commitlint --edit ${1}
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx @ls-lint/ls-lint && npx lint-staged --allow-empty
|
|
@ -0,0 +1,30 @@
|
|||
ls:
|
||||
packages/**:
|
||||
.dir: kebab-case | regex:__[a-z0-9]+__
|
||||
.scss: kebab-case
|
||||
.vue: kebab-case
|
||||
.js: kebab-case | pointcase
|
||||
.ts: kebab-case | pointcase
|
||||
.tsx: kebab-case | pointcase
|
||||
.component.tsx: kebab-case
|
||||
.props.ts: kebab-case
|
||||
.spec.ts: kebab-case
|
||||
.spec.tsx: kebab-case
|
||||
.route.ts: kebab-case
|
||||
.type.ts: kebab-case
|
||||
.d.ts: kebab-case
|
||||
|
||||
ignore:
|
||||
# docs-vue
|
||||
- packages/docs-vue/.vscode
|
||||
- packages/docs-vue/public
|
||||
- packages/docs-vue/node_modules
|
||||
- packages/docs-vue/dist
|
||||
# ui-vue
|
||||
- packages/ui-vue/.vscode
|
||||
- packages/ui-vue/node_modules
|
||||
- packages/ui-vue/dist
|
||||
- packages/ui-vue/public
|
||||
# eslint-config
|
||||
- packages/eslint-config/node_modules
|
||||
- packages/eslint-config/test
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": true,
|
||||
"jsxSingleQuote": false,
|
||||
"printWidth": 140,
|
||||
"semi": true,
|
||||
"useTabs": false,
|
||||
"trailingComma": "none",
|
||||
"singleQuote": true,
|
||||
"tabWidth": 4,
|
||||
"endOfLine": "auto",
|
||||
"proseWrap": "preserve"
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"extends": [
|
||||
"stylelint-config-standard",
|
||||
"stylelint-config-recommended-scss"
|
||||
],
|
||||
"plugins": [
|
||||
"stylelint-scss"
|
||||
],
|
||||
"rules": {
|
||||
"string-quotes": "single",
|
||||
"property-no-unknown": true,
|
||||
"selector-pseudo-class-no-unknown": true,
|
||||
"at-rule-empty-line-before": ["always",{
|
||||
"except":["blockless-after-same-name-blockless","first-nested","inside-block"],
|
||||
"ignore": ["after-comment", "first-nested"]
|
||||
}],
|
||||
"rule-empty-line-before":["always",{
|
||||
"except": [ "after-single-line-comment", "first-nested"]
|
||||
}],
|
||||
"block-no-empty": true,
|
||||
"selector-pseudo-element-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoElements": [
|
||||
"ng-deep"
|
||||
]
|
||||
}
|
||||
],
|
||||
"selector-type-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreTypes": [
|
||||
"/^d-/"
|
||||
]
|
||||
}
|
||||
],
|
||||
"color-hex-length": "long",
|
||||
"no-descending-specificity": null,
|
||||
"font-family-no-missing-generic-family-keyword": null,
|
||||
"no-duplicate-selectors": null,
|
||||
"declaration-block-no-duplicate-properties": [
|
||||
true,
|
||||
{
|
||||
"ignore": [
|
||||
"consecutive-duplicates"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
|
@ -1,110 +1,112 @@
|
|||
const types = ['config', 'feature', 'fix', 'docs', 'style', 'refactor', 'performance', 'test', 'build', 'release', 'chore', 'revert'];
|
||||
|
||||
module.exports = {
|
||||
parserPreset: { parserOpts: { headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/ } },
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'type-empty': [2, 'never'],
|
||||
'type-enum': [2, 'always', types],
|
||||
'scope-case': [0, 'always'],
|
||||
'subject-empty': [2, 'never'],
|
||||
'subject-case': [0, 'never'],
|
||||
'header-max-length': [2, 'always', 88],
|
||||
},
|
||||
prompt: {
|
||||
questions: {
|
||||
type: {
|
||||
description: "Select the type of change that you're committing",
|
||||
enum: {
|
||||
config: {
|
||||
description: 'Changes that affect the tools, such as eslint, npm, vscode.',
|
||||
title: 'Config',
|
||||
emoji: '🛠',
|
||||
},
|
||||
feature: {
|
||||
description: 'A new feature',
|
||||
title: 'Features',
|
||||
emoji: '✨',
|
||||
},
|
||||
fix: {
|
||||
description: 'A bug fix',
|
||||
title: 'Bug Fixes',
|
||||
emoji: '🐛',
|
||||
},
|
||||
docs: {
|
||||
description: 'Documentation only changes',
|
||||
title: 'Documentation',
|
||||
emoji: '📚',
|
||||
},
|
||||
style: {
|
||||
description: 'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)',
|
||||
title: 'Styles',
|
||||
emoji: '💎',
|
||||
},
|
||||
refactor: {
|
||||
description: 'A code change that neither fixes a bug nor adds a feature',
|
||||
title: 'Code Refactoring',
|
||||
emoji: '📦',
|
||||
},
|
||||
performance: {
|
||||
description: 'A code change that improves performance',
|
||||
title: 'Performance Improvements',
|
||||
emoji: '🚀',
|
||||
},
|
||||
test: {
|
||||
description: 'Adding missing tests or correcting existing tests',
|
||||
title: 'Tests',
|
||||
emoji: '🚨',
|
||||
},
|
||||
build: {
|
||||
description: 'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)',
|
||||
title: 'Builds',
|
||||
emoji: '🛠',
|
||||
},
|
||||
ci: {
|
||||
description: 'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)',
|
||||
title: 'Continuous Integrations',
|
||||
emoji: '⚙️',
|
||||
},
|
||||
chore: {
|
||||
description: "Other changes that don't modify src or test files",
|
||||
title: 'Chores',
|
||||
emoji: '♻️',
|
||||
},
|
||||
revert: {
|
||||
description: 'Reverts a previous commit',
|
||||
title: 'Reverts',
|
||||
emoji: '🗑',
|
||||
},
|
||||
},
|
||||
},
|
||||
scope: {
|
||||
description: 'What is the scope of this change (e.g. component or file name)',
|
||||
},
|
||||
subject: {
|
||||
description: 'Write a short, imperative tense description of the change',
|
||||
},
|
||||
body: {
|
||||
description: 'Provide a longer description of the change',
|
||||
},
|
||||
isBreaking: {
|
||||
description: 'Are there any breaking changes?',
|
||||
},
|
||||
breakingBody: {
|
||||
description: 'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself',
|
||||
},
|
||||
breaking: {
|
||||
description: 'Describe the breaking changes',
|
||||
},
|
||||
isIssueAffected: {
|
||||
description: 'Does this change affect any open issues?',
|
||||
},
|
||||
issuesBody: {
|
||||
description: 'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself',
|
||||
},
|
||||
issues: {
|
||||
description: 'Add issue references (e.g. "fix #123", "re #123".)',
|
||||
},
|
||||
parserPreset: { parserOpts: { headerPattern: /^(\w*)(?:\((.*)\))?!?: (.*)$/ } },
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'type-empty': [2, 'never'],
|
||||
'type-enum': [2, 'always', types],
|
||||
'scope-case': [0, 'always'],
|
||||
'subject-empty': [2, 'never'],
|
||||
'subject-case': [0, 'never'],
|
||||
'header-max-length': [2, 'always', 88],
|
||||
},
|
||||
prompt: {
|
||||
questions: {
|
||||
type: {
|
||||
description: "Select the type of change that you're committing",
|
||||
enum: {
|
||||
config: {
|
||||
description: 'Changes that affect the tools, such as eslint, npm, vscode.',
|
||||
title: 'Config',
|
||||
emoji: '🛠',
|
||||
},
|
||||
feature: {
|
||||
description: 'A new feature',
|
||||
title: 'Features',
|
||||
emoji: '✨',
|
||||
},
|
||||
fix: {
|
||||
description: 'A bug fix',
|
||||
title: 'Bug Fixes',
|
||||
emoji: '🐛',
|
||||
},
|
||||
docs: {
|
||||
description: 'Documentation only changes',
|
||||
title: 'Documentation',
|
||||
emoji: '📚',
|
||||
},
|
||||
style: {
|
||||
description:
|
||||
'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)',
|
||||
title: 'Styles',
|
||||
emoji: '💎',
|
||||
},
|
||||
refactor: {
|
||||
description: 'A code change that neither fixes a bug nor adds a feature',
|
||||
title: 'Code Refactoring',
|
||||
emoji: '📦',
|
||||
},
|
||||
performance: {
|
||||
description: 'A code change that improves performance',
|
||||
title: 'Performance Improvements',
|
||||
emoji: '🚀',
|
||||
},
|
||||
test: {
|
||||
description: 'Adding missing tests or correcting existing tests',
|
||||
title: 'Tests',
|
||||
emoji: '🚨',
|
||||
},
|
||||
build: {
|
||||
description: 'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)',
|
||||
title: 'Builds',
|
||||
emoji: '🛠',
|
||||
},
|
||||
ci: {
|
||||
description:
|
||||
'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)',
|
||||
title: 'Continuous Integrations',
|
||||
emoji: '⚙️',
|
||||
},
|
||||
chore: {
|
||||
description: "Other changes that don't modify src or test files",
|
||||
title: 'Chores',
|
||||
emoji: '♻️',
|
||||
},
|
||||
revert: {
|
||||
description: 'Reverts a previous commit',
|
||||
title: 'Reverts',
|
||||
emoji: '🗑',
|
||||
},
|
||||
},
|
||||
},
|
||||
scope: {
|
||||
description: 'What is the scope of this change (e.g. component or file name)',
|
||||
},
|
||||
subject: {
|
||||
description: 'Write a short, imperative tense description of the change',
|
||||
},
|
||||
body: {
|
||||
description: 'Provide a longer description of the change',
|
||||
},
|
||||
isBreaking: {
|
||||
description: 'Are there any breaking changes?',
|
||||
},
|
||||
breakingBody: {
|
||||
description: 'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself',
|
||||
},
|
||||
breaking: {
|
||||
description: 'Describe the breaking changes',
|
||||
},
|
||||
isIssueAffected: {
|
||||
description: 'Does this change affect any open issues?',
|
||||
},
|
||||
issuesBody: {
|
||||
description: 'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself',
|
||||
},
|
||||
issues: {
|
||||
description: 'Add issue references (e.g. "fix #123", "re #123".)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>();
|
||||
|
||||
const count = ref(0);
|
||||
const count = ref(0)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -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>
|
|
@ -0,0 +1,15 @@
|
|||
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);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
import type { App } from 'vue';
|
||||
import Avatar from './src/avatar.component';
|
||||
|
||||
export * from './src/avatar.props';
|
||||
|
||||
export { Avatar };
|
||||
|
||||
export default {
|
||||
install(app: App): void {
|
||||
app.component(Avatar.name, Avatar);
|
||||
}
|
||||
};
|
|
@ -1,66 +1,68 @@
|
|||
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'],
|
||||
setup(props: AvatarProps, context: SetupContext) {
|
||||
const avatarClass = computed(() => ({
|
||||
'f-avatar': true,
|
||||
'f-avatar-readonly': props.readonly,
|
||||
'f-avatar-circle': props.shape === 'circle',
|
||||
'f-avatar-square': props.shape === 'square',
|
||||
}));
|
||||
name: 'Avatar',
|
||||
props: avatarProps,
|
||||
emits: ['change', 'update:modelValue'],
|
||||
setup(props: AvatarProps, context: SetupContext) {
|
||||
const avatarClass = computed(() => ({
|
||||
'f-avatar': true,
|
||||
'f-avatar-readonly': props.readonly,
|
||||
'f-avatar-circle': props.shape === 'circle',
|
||||
'f-avatar-square': props.shape === 'square'
|
||||
}));
|
||||
|
||||
const avatarStyle = computed(() => ({
|
||||
width: props.avatarWidth + 'px',
|
||||
height: props.avatarHeight + 'px',
|
||||
}));
|
||||
const modelValue = ref(props.modelValue);
|
||||
|
||||
let showLoading = false;
|
||||
let imgSrc = '';
|
||||
const avatarStyle = computed(() => ({
|
||||
width: props.avatarWidth + 'px',
|
||||
height: props.avatarHeight + 'px'
|
||||
}));
|
||||
|
||||
const currentImgType = ['image/image', 'image/webp', 'image/png', 'image/svg', 'image/gif', 'image/jpg', 'image/jpeg', 'image/bmp'];
|
||||
let showLoading = false;
|
||||
let imgSrc = '';
|
||||
|
||||
function errorSrc() {
|
||||
return '';
|
||||
const currentImgType = ['image/image', 'image/webp', 'image/png', 'image/svg', 'image/gif', 'image/jpg', 'image/jpeg', 'image/bmp'];
|
||||
|
||||
function errorSrc() {
|
||||
return '';
|
||||
}
|
||||
|
||||
function getfiledata() {}
|
||||
|
||||
const file=ref(null);
|
||||
|
||||
const { acceptTypes, imageSource,onClickImage } = useImage(props, context, file, modelValue);
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<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={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={acceptTypes.value}
|
||||
onChange={getfiledata}
|
||||
style="display: none;"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function getfiledata() {}
|
||||
|
||||
const defaultImgSrc =
|
||||
'data:image/jpeg;base64,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';
|
||||
const errorImgSrc =
|
||||
'data:image/jpeg;base64,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';
|
||||
|
||||
const imageType = computed(() => props.type.join());
|
||||
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<div class={avatarClass.value} style={avatarStyle.value}>
|
||||
{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()} />
|
||||
{!props.readonly && (
|
||||
<div class="f-avatar-icon">
|
||||
<span class="f-icon f-icon-camera"></span>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
name="file-input"
|
||||
type="file"
|
||||
class="f-avatar-upload"
|
||||
accept={imageType.value}
|
||||
onChange={getfiledata}
|
||||
style="display: none;"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
.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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,21 @@
|
|||
import { ComputedRef } from 'vue';
|
||||
|
||||
export interface UseImage {
|
||||
acceptTypes: ComputedRef<string>;
|
||||
acceptTypes: ComputedRef<string>;
|
||||
|
||||
imageSrc: ComputedRef<string>;
|
||||
imageSource: ComputedRef<string>;
|
||||
|
||||
imageTitle: ComputedRef<string>;
|
||||
imageTitle: ComputedRef<string>;
|
||||
|
||||
onClickImage: () => void;
|
||||
}
|
||||
|
||||
export interface ImageFile {
|
||||
size: number;
|
||||
name: string;
|
||||
type: string;
|
||||
lastModified?: string;
|
||||
lastModifiedDate?: Date;
|
||||
state?: string;
|
||||
base64?: string;
|
||||
size: number;
|
||||
name: string;
|
||||
type: string;
|
||||
lastModified?: string;
|
||||
lastModifiedDate?: Date;
|
||||
state?: string;
|
||||
base64?: string;
|
||||
}
|
||||
|
|
|
@ -5,88 +5,90 @@ import { AvatarProps } from '../avatar.props';
|
|||
import { ImageFile } from './types';
|
||||
|
||||
export function useFile(props: AvatarProps, context: SetupContext, allowTypes: string[]) {
|
||||
function getFileData($event: Event) {
|
||||
if (props.readonly) {
|
||||
return;
|
||||
}
|
||||
const fileInput = $event.target as HTMLInputElement;
|
||||
const selectedFiles = fileInput.files;
|
||||
if (!selectedFiles || !selectedFiles[0]) {
|
||||
return;
|
||||
}
|
||||
const fileType = selectedFiles[0].type;
|
||||
const isLtSize = selectedFiles[0].size / 1024 / 1024 < props.maxSize;
|
||||
if (allowTypes.indexOf(fileType) < 0) {
|
||||
const typeErrorMessage = this.localeService.getValue('avatar.typeError');
|
||||
this.notifyService.error({
|
||||
type: 'error',
|
||||
title: '',
|
||||
msg: typeErrorMessage,
|
||||
});
|
||||
fileInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLtSize) {
|
||||
const sizeErrorMessageTemplate = this.localeService.getValue('avatar.sizeError');
|
||||
const sizeErrorMessage: string = sizeErrorMessageTemplate + props.maxSize + 'MB!';
|
||||
this.notifyService.error({
|
||||
type: 'error',
|
||||
title: '',
|
||||
msg: sizeErrorMessage,
|
||||
});
|
||||
fileInput.value = '';
|
||||
return;
|
||||
}
|
||||
this.transformFile(selectedFiles[0]);
|
||||
fileInput.value = '';
|
||||
}
|
||||
|
||||
function readSourceFile(sourceFile: File): Promise<{ state: string; content: string | ArrayBuffer | null }> {
|
||||
const result = new Promise<{ state: string; content: string | ArrayBuffer | null }>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(sourceFile);
|
||||
reader.onload = () => {
|
||||
resolve({ state: 'done', content: reader.result });
|
||||
};
|
||||
reader.onerror = function () {
|
||||
reject({ state: 'error', content: reader.result });
|
||||
};
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function updateImageSrc(imageSrc: string) {}
|
||||
|
||||
function removeBase64(imageSrc: string) {}
|
||||
|
||||
function transformFile(sourceFile: File) {
|
||||
// const subject = new Subject();
|
||||
const imageFile: ImageFile = {
|
||||
size: sourceFile.size,
|
||||
name: sourceFile.name,
|
||||
type: sourceFile.type,
|
||||
lastModified: String(sourceFile.lastModified),
|
||||
};
|
||||
readSourceFile(sourceFile)
|
||||
.then((result) => {
|
||||
if (result.state === 'done') {
|
||||
updateImageSrc(result.content as string);
|
||||
const imageFileContent = removeBase64(imageSrc);
|
||||
context.emit('change', imageFileContent);
|
||||
context.emit('touched');
|
||||
imageFile.state = result.state;
|
||||
imageFile.base64 = result.content as string;
|
||||
context.emit('imageChange', imageFile);
|
||||
function getFileData($event: Event) {
|
||||
if (props.readonly) {
|
||||
return;
|
||||
}
|
||||
})
|
||||
.catch((reason) => {
|
||||
const uploadErrorText = this.localeService.getValue('avatar.uploadError');
|
||||
this.notifyService.error({
|
||||
type: 'error',
|
||||
title: '',
|
||||
msg: uploadErrorText,
|
||||
const fileInput = $event.target as HTMLInputElement;
|
||||
const selectedFiles = fileInput.files;
|
||||
if (!selectedFiles || !selectedFiles[0]) {
|
||||
return;
|
||||
}
|
||||
const fileType = selectedFiles[0].type;
|
||||
const isLtSize = selectedFiles[0].size / 1024 / 1024 < props.maxSize;
|
||||
if (allowTypes.indexOf(fileType) < 0) {
|
||||
const typeErrorMessage = this.localeService.getValue('avatar.typeError');
|
||||
this.notifyService.error({
|
||||
type: 'error',
|
||||
title: '',
|
||||
msg: typeErrorMessage
|
||||
});
|
||||
fileInput.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLtSize) {
|
||||
const sizeErrorMessageTemplate = this.localeService.getValue('avatar.sizeError');
|
||||
const sizeErrorMessage: string = sizeErrorMessageTemplate + props.maxSize + 'MB!';
|
||||
this.notifyService.error({
|
||||
type: 'error',
|
||||
title: '',
|
||||
msg: sizeErrorMessage
|
||||
});
|
||||
fileInput.value = '';
|
||||
return;
|
||||
}
|
||||
this.transformFile(selectedFiles[0]);
|
||||
fileInput.value = '';
|
||||
}
|
||||
|
||||
function readSourceFile(sourceFile: File): Promise<{ state: string; content: string | ArrayBuffer | null }> {
|
||||
const result = new Promise<{ state: string; content: string | ArrayBuffer | null }>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(sourceFile);
|
||||
reader.onload = () => {
|
||||
resolve({ state: 'done', content: reader.result });
|
||||
};
|
||||
reader.onerror = function () {
|
||||
reject({ state: 'error', content: reader.result });
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function updateImageSrc(imageSrc: string) {}
|
||||
|
||||
function removeBase64(imageSrc: string) {}
|
||||
|
||||
function transformFile(sourceFile: File) {
|
||||
// const subject = new Subject();
|
||||
const imageFile: ImageFile = {
|
||||
size: sourceFile.size,
|
||||
name: sourceFile.name,
|
||||
type: sourceFile.type,
|
||||
lastModified: String(sourceFile.lastModified)
|
||||
};
|
||||
readSourceFile(sourceFile)
|
||||
.then((result) => {
|
||||
if (result.state === 'done') {
|
||||
updateImageSrc(result.content as string);
|
||||
const imageFileContent = removeBase64(imageSrc);
|
||||
context.emit('change', imageFileContent);
|
||||
context.emit('touched');
|
||||
imageFile.state = result.state;
|
||||
imageFile.base64 = result.content as string;
|
||||
context.emit('imageChange', imageFile);
|
||||
}
|
||||
})
|
||||
.catch((reason) => {
|
||||
const uploadErrorText = this.localeService.getValue('avatar.uploadError');
|
||||
this.notifyService.error({
|
||||
type: 'error',
|
||||
title: '',
|
||||
msg: uploadErrorText
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,71 +1,71 @@
|
|||
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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';
|
||||
|
||||
// 判断是否是图片路径
|
||||
function isUrl(url: string) {
|
||||
return url.match(/\.(jpeg|jpg|gif|png|svg|bmp|webp)$/) != null;
|
||||
}
|
||||
const readonly = ref(props.readonly);
|
||||
|
||||
// 判断是否是完成base64
|
||||
function isBase64Image(url: string) {
|
||||
return url.indexOf('data:image/') > -1;
|
||||
}
|
||||
|
||||
function appendBase64ImageHeader(val) {
|
||||
if (!val) {
|
||||
return '';
|
||||
// 判断是否是图片路径
|
||||
function isUrl(url: string) {
|
||||
return url.match(/\.(jpeg|jpg|gif|png|svg|bmp|webp)$/) != null;
|
||||
}
|
||||
return 'data:image/jpeg;base64,' + val;
|
||||
}
|
||||
|
||||
const acceptTypes = computed(() => {
|
||||
if (!props.type || !props.type.length) {
|
||||
return '';
|
||||
// 判断是否是完成base64
|
||||
function isBase64Image(url: string) {
|
||||
return url.indexOf('data:image/') > -1;
|
||||
}
|
||||
const imageTypesArray = props.type.map((fileType: string) => {
|
||||
if (fileType === 'jpg') {
|
||||
fileType = 'jpeg';
|
||||
}
|
||||
return `image/${fileType}`;
|
||||
|
||||
function appendBase64ImageHeader(val) {
|
||||
if (!val) {
|
||||
return '';
|
||||
}
|
||||
return 'data:image/jpeg;base64,' + val;
|
||||
}
|
||||
|
||||
const acceptTypes = computed(() => {
|
||||
if (!props.type || !props.type.length) {
|
||||
return '';
|
||||
}
|
||||
const imageTypesArray = props.type.map((fileType: string) => {
|
||||
if (fileType === 'jpg') {
|
||||
fileType = 'jpeg';
|
||||
}
|
||||
return `image/${fileType}`;
|
||||
});
|
||||
if (!imageTypesArray || !imageTypesArray.length) {
|
||||
return 'image/*';
|
||||
}
|
||||
return imageTypesArray.join(',');
|
||||
});
|
||||
if (!imageTypesArray || !imageTypesArray.length) {
|
||||
return 'image/*';
|
||||
}
|
||||
return imageTypesArray.join(',');
|
||||
});
|
||||
|
||||
const imageSrc = computed(() => {
|
||||
if (!props.cover) {
|
||||
return defaultImage;
|
||||
}
|
||||
if (isUrl(props.cover)) {
|
||||
return props.cover;
|
||||
}
|
||||
if (isBase64Image(props.cover)) {
|
||||
return props.cover;
|
||||
}
|
||||
return appendBase64ImageHeader(props.cover);
|
||||
});
|
||||
const imageSource = computed(() => {
|
||||
const image = modelValue.value || props.cover || defaultImage;
|
||||
if (isUrl(image) || isBase64Image(image)) {
|
||||
return image;
|
||||
}
|
||||
return appendBase64ImageHeader(image);
|
||||
});
|
||||
|
||||
const imageTitle = computed(() => {
|
||||
return props.readonly ? '' : props.tile;
|
||||
});
|
||||
const imageTitle = computed(() => {
|
||||
return readonly.value ? '' : props.tile;
|
||||
});
|
||||
|
||||
function onClickImage() {
|
||||
if (this.readonly) {
|
||||
return;
|
||||
function onClickImage() {
|
||||
if (readonly.value) {
|
||||
return;
|
||||
}
|
||||
fileInput && fileInput.value && fileInput.value.click();
|
||||
}
|
||||
fileInput.click();
|
||||
}
|
||||
|
||||
function getImageFile() {
|
||||
return this.imgFileObj;
|
||||
}
|
||||
function getImageFile() {
|
||||
return this.imgFileObj;
|
||||
}
|
||||
|
||||
return { acceptTypes, imageSrc, imageTitle };
|
||||
return { acceptTypes, imageSource, imageTitle, onClickImage };
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@ export { ButtonEdit };
|
|||
export default {
|
||||
install(app: App): void {
|
||||
app.component(ButtonEdit.name, ButtonEdit);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,7 +18,7 @@ export function useTextBox(props: ButtonEditProps, context: SetupContext, modelV
|
|||
'text-center': props.textAlign === 'center',
|
||||
'text-right': props.textAlign === 'right',
|
||||
'form-control': true,
|
||||
'f-utils-fill': true,
|
||||
'f-utils-fill': true
|
||||
}));
|
||||
|
||||
function changeTextBoxValue(newValue: string, showEmitChangeEmit = true) {
|
||||
|
@ -27,6 +27,7 @@ export function useTextBox(props: ButtonEditProps, context: SetupContext, modelV
|
|||
if (showEmitChangeEmit) {
|
||||
context.emit('change', newValue);
|
||||
}
|
||||
context.emit('update:modelValue', newValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +62,7 @@ export function useTextBox(props: ButtonEditProps, context: SetupContext, modelV
|
|||
displayText.value = newValue;
|
||||
if (modelValue.value !== newValue) {
|
||||
changeTextBoxValue(newValue, false);
|
||||
context.emit('update:modelValue', ($event.target as HTMLInputElement).value);
|
||||
// context.emit('update:modelValue', ($event.target as HTMLInputElement).value);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,6 +101,6 @@ export function useTextBox(props: ButtonEditProps, context: SetupContext, modelV
|
|||
onKeyDownTextBox,
|
||||
onKeyUpTextBox,
|
||||
onMouseDownTextBox,
|
||||
onTextBoxValueChange,
|
||||
onTextBoxValueChange
|
||||
};
|
||||
}
|
||||
|
|
|
@ -3,206 +3,206 @@ import { ref } from 'vue';
|
|||
import { ButtonEdit } from '..';
|
||||
|
||||
describe('f-button-edit', () => {
|
||||
const mocks = {};
|
||||
const mocks = {};
|
||||
|
||||
beforeAll(() => {});
|
||||
beforeAll(() => {});
|
||||
|
||||
describe('properties', () => {
|
||||
it('should has default props', () => {});
|
||||
it('should has auto complete', () => {});
|
||||
it('should net has auto complete', () => {});
|
||||
it('should be disabled', () => {});
|
||||
it('should net be disabled', () => {});
|
||||
it('should be editable', () => {
|
||||
const wrapper = mount({
|
||||
setup(props, ctx) {
|
||||
return () => {
|
||||
return <ButtonEdit editable={true}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(wrapper.find('div').find('div').find('input').attributes.readonly).toBeFalsy();
|
||||
});
|
||||
it('should not be editable', () => {
|
||||
const wrapper = mount({
|
||||
setup(props, ctx) {
|
||||
return () => {
|
||||
return <ButtonEdit editable={false}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(wrapper.find('.f-cmp-inputgroup').exists()).toBeTruthy();
|
||||
expect(wrapper.find('div').find('div').find('input').find('[readonly]').exists).toBeTruthy();
|
||||
});
|
||||
it('should show clear button', () => {});
|
||||
it('should not show clear button', () => {});
|
||||
it('should be readonly', () => {});
|
||||
it('should not be readonly', () => {});
|
||||
it('should enable text alignment', () => {});
|
||||
it('should show append button even be disabled', () => {});
|
||||
it('should has title', () => {});
|
||||
it('should has type', () => {});
|
||||
it('should has placeholder', () => {});
|
||||
it('should has min length', () => {});
|
||||
it('should has max length', () => {});
|
||||
it('should has tab index', () => {});
|
||||
describe('properties', () => {
|
||||
it('should has default props', () => {});
|
||||
it('should has auto complete', () => {});
|
||||
it('should net has auto complete', () => {});
|
||||
it('should be disabled', () => {});
|
||||
it('should net be disabled', () => {});
|
||||
it('should be editable', () => {
|
||||
const wrapper = mount({
|
||||
setup(props, ctx) {
|
||||
return () => {
|
||||
return <ButtonEdit editable={true}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(wrapper.find('div').find('div').find('input').attributes['readonly']).toBeFalsy();
|
||||
});
|
||||
it('should not be editable', () => {
|
||||
const wrapper = mount({
|
||||
setup(props, ctx) {
|
||||
return () => {
|
||||
return <ButtonEdit editable={false}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
expect(wrapper.find('.f-cmp-inputgroup').exists()).toBeTruthy();
|
||||
expect(wrapper.find('div').find('div').find('input').find('[readonly]').exists).toBeTruthy();
|
||||
});
|
||||
it('should show clear button', () => {});
|
||||
it('should not show clear button', () => {});
|
||||
it('should be readonly', () => {});
|
||||
it('should not be readonly', () => {});
|
||||
it('should enable text alignment', () => {});
|
||||
it('should show append button even be disabled', () => {});
|
||||
it('should has title', () => {});
|
||||
it('should has type', () => {});
|
||||
it('should has placeholder', () => {});
|
||||
it('should has min length', () => {});
|
||||
it('should has max length', () => {});
|
||||
it('should has tab index', () => {});
|
||||
});
|
||||
|
||||
describe('render', () => {});
|
||||
|
||||
describe('methods', () => {});
|
||||
|
||||
describe('events', () => {
|
||||
it('should emit event named clear when click clear button', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit enableClear onClear={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('.input-group-clear').trigger('click');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
describe('render', () => {});
|
||||
|
||||
describe('methods', () => {});
|
||||
|
||||
describe('events', () => {
|
||||
it('should emit event named clear when click clear button', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit enableClear onClear={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('.input-group-clear').trigger('click');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named change when changed text box value', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const num = ref('0');
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit v-model={num.value} onChange={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').setValue('test');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named click whend click text box', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onClick={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('click');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named clickButton when click append button', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onClickButton={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('.input-group-append-button').trigger('click');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named blur when text box lost focus', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onBlur={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('blur');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named focus when text box get focus', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onFocus={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('focus');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named mouseEnterIcon when mouse move in append button', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onMouseEnterIcon={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('.input-group-append-button').trigger('mouseenter');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named mouseLeaveIcon when mouse leave append button', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onMouseLeaveIcon={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('.input-group-append-button').trigger('mouseleave');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named keyup when input text in text box', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onKeyup={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('keyup');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named keydown when input text in text box', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onKeydown={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('keydown');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named input when input text in text box', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onInput={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('input');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
it('should emit event named change when changed text box value', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const num = ref('0');
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit v-model={num.value} onChange={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').setValue('test');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
describe('behaviors', () => {
|
||||
it('should hightlight text box when mouse in', () => {});
|
||||
it('should show clear button when mouse in text box', () => {});
|
||||
it('should show clear button when fouse text box and it not empty', () => {});
|
||||
it('should hide clear button when text box is emtyp', () => {});
|
||||
it('should show clear button when text any word from empty', () => {});
|
||||
it('should emit event named click whend click text box', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onClick={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('click');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named clickButton when click append button', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onClickButton={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('.input-group-append-button').trigger('click');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named blur when text box lost focus', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onBlur={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('blur');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named focus when text box get focus', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onFocus={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('focus');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named mouseEnterIcon when mouse move in append button', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onMouseEnterIcon={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('.input-group-append-button').trigger('mouseenter');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named mouseLeaveIcon when mouse leave append button', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onMouseLeaveIcon={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('.input-group-append-button').trigger('mouseleave');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named keyup when input text in text box', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onKeyup={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('keyup');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named keydown when input text in text box', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onKeydown={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('keydown');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
|
||||
it('should emit event named input when input text in text box', async () => {
|
||||
const handleClick = jest.fn();
|
||||
const wrapper = mount({
|
||||
setup() {
|
||||
return () => {
|
||||
return <ButtonEdit onInput={handleClick}></ButtonEdit>;
|
||||
};
|
||||
},
|
||||
});
|
||||
await wrapper.find('div').find('div').find('input').trigger('input');
|
||||
expect(handleClick).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('behaviors', () => {
|
||||
it('should hightlight text box when mouse in', () => {});
|
||||
it('should show clear button when mouse in text box', () => {});
|
||||
it('should show clear button when fouse text box and it not empty', () => {});
|
||||
it('should hide clear button when text box is emtyp', () => {});
|
||||
it('should show clear button when text any word from empty', () => {});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import type { App } from 'vue';
|
||||
import Notify from './src/notify.component';
|
||||
import Toast from './src/components/toast.component';
|
||||
|
||||
export * from './src/notify.props';
|
||||
export * from './src/components/toast.props';
|
||||
|
||||
export { Notify, Toast };
|
||||
|
||||
export default {
|
||||
install(app: App): void {
|
||||
app.component(Notify.name, Notify);
|
||||
app.component(Toast.name, Toast);
|
||||
}
|
||||
};
|
|
@ -1 +1,12 @@
|
|||
import type { App } from 'vue';
|
||||
import Popover from './src/popover.component';
|
||||
|
||||
export * from './src/popover.props';
|
||||
|
||||
export { Popover };
|
||||
|
||||
export default {
|
||||
install(app: App): void {
|
||||
app.component(Popover.name, Popover);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import type { App } from 'vue';
|
||||
import Section from './src/section.component';
|
||||
|
||||
export * from './src/section.props';
|
||||
export { Section };
|
||||
|
||||
export default {
|
||||
install(app: App): void {
|
||||
app.component(Section.name, Section);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,92 @@
|
|||
import { computed, defineComponent, ref, SetupContext } from 'vue';
|
||||
import { ButtonConfig, SectionProps, sectionProps } from './section.props';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FSection',
|
||||
props: sectionProps,
|
||||
emits: [],
|
||||
setup(props: SectionProps, context: SetupContext) {
|
||||
const shouldShowHeader = computed(() => {
|
||||
return true;
|
||||
});
|
||||
|
||||
const shouldShowToolbarInHeader = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const shouldShowToolbarInContent = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const shouldShowToolbarTemplateInContent = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const shouldShowExtendArea = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const toolbarButtons = ref([]);
|
||||
|
||||
const headerClass = computed(() => ({}));
|
||||
|
||||
const extendAreaClass = computed(() => ({}));
|
||||
|
||||
const contentClass = computed(() => ({
|
||||
'f-section-content': true,
|
||||
}));
|
||||
|
||||
function getToolbarState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,36 @@
|
|||
import { ExtractPropTypes, PropType } from 'vue';
|
||||
|
||||
export interface ButtonConfig {
|
||||
id: string;
|
||||
disable: boolean;
|
||||
title: string;
|
||||
click: any;
|
||||
appearance: object;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolbarConfig {
|
||||
position: string;
|
||||
contents: ButtonConfig[];
|
||||
}
|
||||
|
||||
export const sectionProps = {
|
||||
contentClass: { type: String, default: '' },
|
||||
maxStatus: { type: Boolean, default: false },
|
||||
enableAccording: { type: Boolean, default: false },
|
||||
enableCollapse: { type: Boolean, default: true },
|
||||
mainTitle: { type: String, default: '' },
|
||||
subTitle: { type: String, default: '' },
|
||||
showHeader: { type: Boolean, default: true },
|
||||
enableMaximize: { type: Boolean, default: false },
|
||||
fill: { type: Boolean, default: false },
|
||||
expandStatus: { type: Boolean, default: true },
|
||||
cotext: { type: Object },
|
||||
index: { type: Number },
|
||||
toolbarPosition: { type: String, default: '' },
|
||||
toolbarButtons: { type: Array<object>, default: [] },
|
||||
toolbar: { type: Object as PropType<ToolbarConfig>, default: {} },
|
||||
showToolbarMoreButton: { type: Boolean, default: true },
|
||||
clickThrottleTime: { type: Number, default: 350 },
|
||||
};
|
||||
export type SectionProps = ExtractPropTypes<typeof sectionProps>;
|
|
@ -0,0 +1,10 @@
|
|||
import type { App } from 'vue';
|
||||
import Switch from './src/switch.component';
|
||||
|
||||
export * from './src/switch.props';
|
||||
|
||||
export default {
|
||||
install(app: App): void {
|
||||
app.component(Switch.name, Switch);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
import type { App } from 'vue';
|
||||
import Tabs from './src/tabs.component';
|
||||
import TabPage from './src/components/tab-page.component';
|
||||
|
||||
export * from './src/tabs.props';
|
||||
export * from './src/components/tab-page.props';
|
||||
|
||||
export { Tabs, TabPage };
|
||||
|
||||
export default {
|
||||
install(app: App): void {
|
||||
app.component(Tabs.name, Tabs);
|
||||
app.component(TabPage.name, TabPage);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
import { defineComponent, SetupContext } from 'vue';
|
||||
import { TabPageProps, tabPageProps } from './tab-page.props';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FTabPage',
|
||||
props: tabPageProps,
|
||||
emits: [],
|
||||
setup(props: TabPageProps, context: SetupContext) {
|
||||
return () => {
|
||||
return context.slots.default && context.slots.default();
|
||||
};
|
||||
}
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
import { ExtractPropTypes } from 'vue';
|
||||
|
||||
export const tabPageProps = {
|
||||
tabWidth: { type: Number, default: -1 },
|
||||
id: { type: String, default: '' },
|
||||
customTitleClass: { type: String, default: '' },
|
||||
titleOverflow: { type: Boolean, default: false },
|
||||
title: { type: String, default: '' },
|
||||
selected: { type: Boolean, default: false },
|
||||
disabled: { type: Boolean, default: false },
|
||||
removeable: { type: Boolean, default: false }
|
||||
};
|
||||
export type TabPageProps = ExtractPropTypes<typeof tabPageProps>;
|
|
@ -0,0 +1,131 @@
|
|||
import { computed, defineComponent, ref, SetupContext } from 'vue';
|
||||
import { TabsProps, tabsProps } from './tabs.props';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FTabs',
|
||||
props: tabsProps,
|
||||
emits: [],
|
||||
setup(props: TabsProps, context: SetupContext) {
|
||||
const hideButtons = ref(false);
|
||||
|
||||
const hideDropDown = ref(false);
|
||||
|
||||
const hasInHeadClass = computed(() => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const fill = ref('');
|
||||
|
||||
const tabType = ref('');
|
||||
|
||||
const position = ref(props.position);
|
||||
|
||||
const tabs = ref([]);
|
||||
|
||||
const shouldShowNavFill = computed(() => {
|
||||
fill.value || tabType.value === 'fill';
|
||||
});
|
||||
|
||||
const shouldShowNavPills = computed(() => {});
|
||||
|
||||
const tabsHeaderClass = computed(() => ({
|
||||
'farris-tabs-header': true,
|
||||
'farris-tabs-inHead': hasInHeadClass.value,
|
||||
'farris-tabs-inContent': !hasInHeadClass.value,
|
||||
'farris-tabs-nav-fill': shouldShowNavFill.value,
|
||||
'farris-tabs-nav-pills': shouldShowNavPills.value,
|
||||
}));
|
||||
|
||||
const tabsTitleStyle = computed(() => ({
|
||||
width: hasInHeadClass.value ? (props.titleWidth ? `${props.titleWidth}%` : '') : '',
|
||||
}));
|
||||
|
||||
const tabsTitleButtonClass = computed(() => ({
|
||||
btn: true,
|
||||
'sc-nav-btn': true,
|
||||
'px-1': true,
|
||||
'sc-nav-lr': true,
|
||||
'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,
|
||||
}));
|
||||
|
||||
const tabContainerClass = computed(() => ({
|
||||
nav: true,
|
||||
'farris-nav-tabs': true,
|
||||
'flex-nowrap': true,
|
||||
'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',
|
||||
}));
|
||||
|
||||
function getTabClass(tab: any) {
|
||||
return {
|
||||
'nav-item': true,
|
||||
'd-none': !tab.show,
|
||||
'f-state-active': tab.id === activeId,
|
||||
'f-state-disable': tab.disabled,
|
||||
};
|
||||
}
|
||||
|
||||
function getTabStyle(tab: any) {
|
||||
return { width: `${tab.tabWidth}px` };
|
||||
}
|
||||
|
||||
function getTabNavLinkClass(tab: any) {
|
||||
return {
|
||||
'nav-link': true,
|
||||
'tabs-text-truncate': true,
|
||||
active: tab.id === activeId,
|
||||
disabled: tab.disabled,
|
||||
};
|
||||
}
|
||||
|
||||
function selectTabByIndex($event: Event, targetTabId: string) {}
|
||||
|
||||
function getTabTextClass(tab: any) {
|
||||
return {
|
||||
'st-tab-text': true,
|
||||
'farris-title-auto': props.autoTitleWidth,
|
||||
'farris-title-text-custom': tab.titleOverflow,
|
||||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
return (
|
||||
<>
|
||||
<div class={tabsHeaderClass.value}>
|
||||
<div class="farris-tabs-title scroll-tabs" style={tabsTitleStyle.value}>
|
||||
<button type="button" class={tabsTitleButtonClass.value}></button>
|
||||
<div class={tabParentClass.value} style="width:100%">
|
||||
<ul class={tabContainerClass.value}>
|
||||
{tabs.value.forEach((tab: any, tabIndex) => {
|
||||
return (
|
||||
<li class={getTabClass(tab)} style={getTabStyle(tab)}>
|
||||
<a class={getTabNavLinkClass(tab)} onClick={($event) => selectTabByIndex($event, tab.id)}>
|
||||
<span class={getTabTextClass(tab)}>{tab.title}</span>
|
||||
{tab.removeable && (
|
||||
<span class="st-drop-close">
|
||||
<i class="f-icon f-icon-close"></i>
|
||||
</span>
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{context.slots.default && context.slots.default()}
|
||||
</>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
import { ExtractPropTypes, PropType } from 'vue';
|
||||
|
||||
export type TabType = 'fill' | 'pills' | 'default';
|
||||
export type TabPosition = 'left' | 'right' | 'top' | 'bottom';
|
||||
|
||||
export const tabsProps = {
|
||||
tabTab: { type: String as PropType<TabType>, default: 'default' },
|
||||
autoTitleWidth: { type: Boolean, default: false },
|
||||
titleLength: { type: Number, default: 7 },
|
||||
position: { type: String as PropType<TabPosition>, default: 'top' },
|
||||
showDropDwon: { type: Boolean, default: false },
|
||||
showTooltips: { type: Boolean, default: false },
|
||||
scrollStep: { type: Number, default: 1 },
|
||||
autoResize: { type: Boolean, default: false },
|
||||
closeable: { type: Boolean, default: false },
|
||||
selectedTab: { type: String, default: '' },
|
||||
width: { type: Number },
|
||||
height: { type: Number },
|
||||
searchBoxVisible: { type: Boolean, default: true },
|
||||
titleWidth: { type: Number, default: 0 },
|
||||
customClass: { type: String, default: '' },
|
||||
};
|
||||
|
||||
export type TabsProps = ExtractPropTypes<typeof tabsProps>;
|
|
@ -0,0 +1,11 @@
|
|||
import type { App } from 'vue';
|
||||
import Text from './src/text.component';
|
||||
|
||||
export * from './src/text.props';
|
||||
export { Text };
|
||||
|
||||
export default {
|
||||
install(app: App): void {
|
||||
app.component(Text.name, Text);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
import type { App } from 'vue';
|
||||
import Tooltip from './src/tooltip.component';
|
||||
|
||||
export * from './src/tooltip.props';
|
||||
|
||||
export { Tooltip };
|
||||
|
||||
export default {
|
||||
install(app: App): void {
|
||||
app.component(Tooltip.name, Tooltip);
|
||||
}
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@farris/ui-vue",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
|
|
|
@ -8,28 +8,29 @@ import FButton from '../components/button/src/button.component';
|
|||
|
||||
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" />
|
||||
<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>
|
||||
<input type="checkbox" id="checkbox" v-model="disable" />
|
||||
<label for="checkbox">disable:{{ disable }}</label>
|
||||
<FButton :disable="disable"></FButton>
|
||||
<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> -->
|
||||
<!-- <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>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import Avatar from '../../components/avatar/src/avatar.component';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<avatar></avatar>
|
||||
</template>
|
|
@ -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>
|
|
@ -1,38 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref } from 'vue';
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
defineProps<{ msg: string }>();
|
||||
|
||||
const count = ref(0)
|
||||
const count = ref(0);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
<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>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank">create-vue</a>, the official Vue + Vite starter
|
||||
</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>
|
||||
<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;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,5 +4,5 @@ import vueJsx from '@vitejs/plugin-vue-jsx';
|
|||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vueJsx()]
|
||||
plugins: [vue(), vueJsx()]
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue