Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
球球不吃虾
vue-vben-admin
提交
305630e3
V
vue-vben-admin
项目概览
球球不吃虾
/
vue-vben-admin
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vue-vben-admin
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
305630e3
编写于
6月 11, 2021
作者:
V
Vben
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat(preview): added createImgPreview picture preview function
上级
3f6920f7
变更
19
隐藏空白更改
内联
并排
Showing
19 changed file
with
460 addition
and
474 deletion
+460
-474
CHANGELOG.zh_CN.md
CHANGELOG.zh_CN.md
+1
-0
src/components/Form/src/components/ApiSelect.vue
src/components/Form/src/components/ApiSelect.vue
+0
-1
src/components/Form/src/components/FormAction.vue
src/components/Form/src/components/FormAction.vue
+0
-2
src/components/Form/src/components/FormItem.vue
src/components/Form/src/components/FormItem.vue
+5
-9
src/components/Form/src/components/RadioButtonGroup.vue
src/components/Form/src/components/RadioButtonGroup.vue
+2
-1
src/components/Form/src/hooks/useAdvanced.ts
src/components/Form/src/hooks/useAdvanced.ts
+0
-2
src/components/Form/src/hooks/useAutoFocus.ts
src/components/Form/src/hooks/useAutoFocus.ts
+9
-3
src/components/Form/src/hooks/useForm.ts
src/components/Form/src/hooks/useForm.ts
+3
-5
src/components/Form/src/hooks/useFormEvents.ts
src/components/Form/src/hooks/useFormEvents.ts
+0
-2
src/components/Form/src/hooks/useFormValues.ts
src/components/Form/src/hooks/useFormValues.ts
+0
-2
src/components/Form/src/types/form.ts
src/components/Form/src/types/form.ts
+0
-1
src/components/Form/src/types/index.ts
src/components/Form/src/types/index.ts
+0
-2
src/components/Preview/src/Functional.vue
src/components/Preview/src/Functional.vue
+436
-0
src/components/Preview/src/functional.ts
src/components/Preview/src/functional.ts
+3
-5
src/components/Preview/src/index.less
src/components/Preview/src/index.less
+0
-118
src/components/Preview/src/index.tsx
src/components/Preview/src/index.tsx
+0
-305
src/components/Preview/src/props.ts
src/components/Preview/src/props.ts
+0
-15
src/components/Preview/src/typing.ts
src/components/Preview/src/typing.ts
+0
-0
src/views/demo/feat/img-preview/index.vue
src/views/demo/feat/img-preview/index.vue
+1
-1
未找到文件。
CHANGELOG.zh_CN.md
浏览文件 @
305630e3
...
...
@@ -9,6 +9,7 @@
-
**CropperImage**
`Cropper`
头像裁剪新增圆形裁剪功能
-
**CropperAvatar**
新增头像上传组件
-
**Drawer**
`useDrawer`
新增
`closeDrawer`
函数
-
**Preview**
新增
`createImgPreview`
图片预览函数
### 🐛 Bug Fixes
...
...
src/components/Form/src/components/ApiSelect.vue
浏览文件 @
305630e3
...
...
@@ -26,7 +26,6 @@
import
{
useRuleFormItem
}
from
'
/@/hooks/component/useFormItem
'
;
import
{
useAttrs
}
from
'
/@/hooks/core/useAttrs
'
;
import
{
get
,
omit
}
from
'
lodash-es
'
;
import
{
LoadingOutlined
}
from
'
@ant-design/icons-vue
'
;
import
{
useI18n
}
from
'
/@/hooks/web/useI18n
'
;
import
{
propTypes
}
from
'
/@/utils/propTypes
'
;
...
...
src/components/Form/src/components/FormAction.vue
浏览文件 @
305630e3
...
...
@@ -40,13 +40,11 @@
<
script
lang=
"ts"
>
import
type
{
ColEx
}
from
'
../types/index
'
;
import
type
{
ButtonProps
}
from
'
ant-design-vue/es/button/buttonTypes
'
;
import
{
defineComponent
,
computed
,
PropType
}
from
'
vue
'
;
import
{
Form
,
Col
}
from
'
ant-design-vue
'
;
import
{
Button
}
from
'
/@/components/Button
'
;
import
{
BasicArrow
}
from
'
/@/components/Basic/index
'
;
import
{
useFormContext
}
from
'
../hooks/useFormContext
'
;
import
{
useI18n
}
from
'
/@/hooks/web/useI18n
'
;
import
{
propTypes
}
from
'
/@/utils/propTypes
'
;
...
...
src/components/Form/src/components/FormItem.vue
浏览文件 @
305630e3
...
...
@@ -4,17 +4,14 @@
import
type
{
FormSchema
}
from
'
../types/form
'
;
import
type
{
ValidationRule
}
from
'
ant-design-vue/lib/form/Form
'
;
import
type
{
TableActionType
}
from
'
/@/components/Table
'
;
import
{
defineComponent
,
computed
,
unref
,
toRefs
}
from
'
vue
'
;
import
{
Form
,
Col
}
from
'
ant-design-vue
'
;
import
{
componentMap
}
from
'
../componentMap
'
;
import
{
BasicHelp
}
from
'
/@/components/Basic
'
;
import
{
isBoolean
,
isFunction
,
isNull
}
from
'
/@/utils/is
'
;
import
{
getSlot
}
from
'
/@/utils/helper/tsxHelper
'
;
import
{
createPlaceholderMessage
,
setComponentRuleType
}
from
'
../helper
'
;
import
{
upperFirst
,
cloneDeep
}
from
'
lodash-es
'
;
import
{
useItemLabelWidth
}
from
'
../hooks/useLabelWidth
'
;
import
{
useI18n
}
from
'
/@/hooks/web/useI18n
'
;
...
...
@@ -91,7 +88,6 @@
if
(
isBoolean
(
dynamicDisabled
))
{
disabled
=
dynamicDisabled
;
}
if
(
isFunction
(
dynamicDisabled
))
{
disabled
=
dynamicDisabled
(
unref
(
getValues
));
}
...
...
@@ -276,7 +272,6 @@
:
{
default
:
()
=>
renderComponentContent
,
};
return
<
Comp
{...
compAttr
}
>
{
compSlot
}
<
/Comp>
;
}
...
...
@@ -317,7 +312,6 @@
};
const
showSuffix
=
!!
suffix
;
const
getSuffix
=
isFunction
(
suffix
)
?
suffix
(
unref
(
getValues
))
:
suffix
;
return
(
...
...
@@ -338,16 +332,18 @@
<
/Form.Item
>
);
}
return
()
=>
{
const
{
colProps
=
{},
colSlot
,
renderColContent
,
component
}
=
props
.
schema
;
if
(
!
componentMap
.
has
(
component
))
return
null
;
if
(
!
componentMap
.
has
(
component
))
{
return
null
;
}
const
{
baseColProps
=
{}
}
=
props
.
formProps
;
const
realColProps
=
{
...
baseColProps
,
...
colProps
};
const
{
isIfShow
,
isShow
}
=
getShow
();
const
values
=
unref
(
getValues
);
const
getContent
=
()
=>
{
return
colSlot
?
getSlot
(
slots
,
colSlot
,
values
)
...
...
src/components/Form/src/components/RadioButtonGroup.vue
浏览文件 @
305630e3
<!--
* @Description:It is troublesome to implement radio button group in the form. So it is extracted independently as a separate component
-->
<
template
>
<RadioGroup
v-bind=
"attrs"
v-model:value=
"state"
button-style=
"solid"
>
<template
v-for=
"item in getOptions"
:key=
"`$
{item.value}`">
...
...
@@ -17,6 +16,7 @@
import
{
isString
}
from
'
/@/utils/is
'
;
import
{
useRuleFormItem
}
from
'
/@/hooks/component/useFormItem
'
;
import
{
useAttrs
}
from
'
/@/hooks/core/useAttrs
'
;
type
OptionsItem
=
{
label
:
string
;
value
:
string
|
number
|
boolean
;
disabled
?:
boolean
};
type
RadioItem
=
string
|
OptionsItem
;
...
...
@@ -39,6 +39,7 @@
const
attrs
=
useAttrs
();
// Embedded in the form, just use the hook binding to perform form verification
const
[
state
]
=
useRuleFormItem
(
props
);
// Processing options value
const
getOptions
=
computed
(():
OptionsItem
[]
=>
{
const
{
options
}
=
props
;
...
...
src/components/Form/src/hooks/useAdvanced.ts
浏览文件 @
305630e3
...
...
@@ -2,10 +2,8 @@ import type { ColEx } from '../types';
import
type
{
AdvanceState
}
from
'
../types/hooks
'
;
import
type
{
ComputedRef
,
Ref
}
from
'
vue
'
;
import
type
{
FormProps
,
FormSchema
}
from
'
../types/form
'
;
import
{
computed
,
unref
,
watch
}
from
'
vue
'
;
import
{
isBoolean
,
isFunction
,
isNumber
,
isObject
}
from
'
/@/utils/is
'
;
import
{
useBreakpoint
}
from
'
/@/hooks/event/useBreakpoint
'
;
import
{
useDebounceFn
}
from
'
@vueuse/core
'
;
...
...
src/components/Form/src/hooks/useAutoFocus.ts
浏览文件 @
305630e3
...
...
@@ -16,16 +16,22 @@ export async function useAutoFocus({
isInitedDefault
,
}:
UseAutoFocusContext
)
{
watchEffect
(
async
()
=>
{
if
(
unref
(
isInitedDefault
)
||
!
unref
(
getProps
).
autoFocusFirstItem
)
return
;
if
(
unref
(
isInitedDefault
)
||
!
unref
(
getProps
).
autoFocusFirstItem
)
{
return
;
}
await
nextTick
();
const
schemas
=
unref
(
getSchema
);
const
formEl
=
unref
(
formElRef
);
const
el
=
(
formEl
as
any
)?.
$el
as
HTMLElement
;
if
(
!
formEl
||
!
el
||
!
schemas
||
schemas
.
length
===
0
)
return
;
if
(
!
formEl
||
!
el
||
!
schemas
||
schemas
.
length
===
0
)
{
return
;
}
const
firstItem
=
schemas
[
0
];
// Only open when the first form item is input type
if
(
!
firstItem
.
component
.
includes
(
'
Input
'
))
return
;
if
(
!
firstItem
.
component
.
includes
(
'
Input
'
))
{
return
;
}
const
inputEl
=
el
.
querySelector
(
'
.ant-row:first-child input
'
)
as
Nullable
<
HTMLInputElement
>
;
if
(
!
inputEl
)
return
;
...
...
src/components/Form/src/hooks/useForm.ts
浏览文件 @
305630e3
import
type
{
FormProps
,
FormActionType
,
UseFormReturnType
,
FormSchema
}
from
'
../types/form
'
;
import
type
{
NamePath
}
from
'
ant-design-vue/lib/form/interface
'
;
import
type
{
DynamicProps
}
from
'
/#/utils
'
;
import
{
ref
,
onUnmounted
,
unref
,
nextTick
,
watch
}
from
'
vue
'
;
import
{
isProdMode
}
from
'
/@/utils/env
'
;
import
{
error
}
from
'
/@/utils/log
'
;
import
{
getDynamicProps
}
from
'
/@/utils
'
;
import
type
{
FormProps
,
FormActionType
,
UseFormReturnType
,
FormSchema
}
from
'
../types/form
'
;
import
type
{
NamePath
}
from
'
ant-design-vue/lib/form/interface
'
;
import
type
{
DynamicProps
}
from
'
/#/utils
'
;
export
declare
type
ValidateFields
=
(
nameList
?:
NamePath
[])
=>
Promise
<
Recordable
>
;
type
Props
=
Partial
<
DynamicProps
<
FormProps
>>
;
...
...
src/components/Form/src/hooks/useFormEvents.ts
浏览文件 @
305630e3
import
type
{
ComputedRef
,
Ref
}
from
'
vue
'
;
import
type
{
FormProps
,
FormSchema
,
FormActionType
}
from
'
../types/form
'
;
import
type
{
NamePath
}
from
'
ant-design-vue/lib/form/interface
'
;
import
{
unref
,
toRaw
}
from
'
vue
'
;
import
{
isArray
,
isFunction
,
isObject
,
isString
}
from
'
/@/utils/is
'
;
import
{
deepMerge
}
from
'
/@/utils
'
;
import
{
dateItemType
,
handleInputNumberValue
}
from
'
../helper
'
;
...
...
src/components/Form/src/hooks/useFormValues.ts
浏览文件 @
305630e3
import
{
isArray
,
isFunction
,
isObject
,
isString
,
isNullOrUnDef
}
from
'
/@/utils/is
'
;
import
{
dateUtil
}
from
'
/@/utils/dateUtil
'
;
import
{
unref
}
from
'
vue
'
;
import
type
{
Ref
,
ComputedRef
}
from
'
vue
'
;
import
type
{
FormProps
,
FormSchema
}
from
'
../types/form
'
;
import
{
set
}
from
'
lodash-es
'
;
interface
UseFormValuesContext
{
...
...
src/components/Form/src/types/form.ts
浏览文件 @
305630e3
import
type
{
NamePath
,
RuleObject
}
from
'
ant-design-vue/lib/form/interface
'
;
import
type
{
VNode
}
from
'
vue
'
;
import
type
{
ButtonProps
as
AntdButtonProps
}
from
'
ant-design-vue/es/button/buttonTypes
'
;
import
type
{
FormItem
}
from
'
./formItem
'
;
import
type
{
ColEx
,
ComponentType
}
from
'
./index
'
;
import
type
{
TableActionType
}
from
'
/@/components/Table/src/types/table
'
;
...
...
src/components/Form/src/types/index.ts
浏览文件 @
305630e3
...
...
@@ -90,9 +90,7 @@ export type ComponentType =
|
'
InputCountDown
'
|
'
Select
'
|
'
ApiSelect
'
|
'
SelectOptGroup
'
|
'
TreeSelect
'
|
'
Transfer
'
|
'
RadioButtonGroup
'
|
'
RadioGroup
'
|
'
Checkbox
'
...
...
src/components/Preview/src/Functional.vue
0 → 100644
浏览文件 @
305630e3
<
script
lang=
"tsx"
>
import
{
defineComponent
,
ref
,
unref
,
computed
,
reactive
,
watchEffect
}
from
'
vue
'
;
import
{
Props
}
from
'
./typing
'
;
import
{
CloseOutlined
,
LeftOutlined
,
RightOutlined
}
from
'
@ant-design/icons-vue
'
;
import
resumeSvg
from
'
/@/assets/svg/preview/resume.svg
'
;
import
rotateSvg
from
'
/@/assets/svg/preview/p-rotate.svg
'
;
import
scaleSvg
from
'
/@/assets/svg/preview/scale.svg
'
;
import
unScaleSvg
from
'
/@/assets/svg/preview/unscale.svg
'
;
import
unRotateSvg
from
'
/@/assets/svg/preview/unrotate.svg
'
;
enum
StatueEnum
{
LOADING
,
DONE
,
FAIL
,
}
interface
ImgState
{
currentUrl
:
string
;
imgScale
:
number
;
imgRotate
:
number
;
imgTop
:
number
;
imgLeft
:
number
;
currentIndex
:
number
;
status
:
StatueEnum
;
moveX
:
number
;
moveY
:
number
;
show
:
boolean
;
}
const
props
=
{
show
:
{
type
:
Boolean
as
PropType
<
boolean
>
,
default
:
false
,
},
imageList
:
{
type
:
[
Array
]
as
PropType
<
string
[]
>
,
default
:
null
,
},
index
:
{
type
:
Number
as
PropType
<
number
>
,
default
:
0
,
},
};
const
prefixCls
=
'
img-preview
'
;
export
default
defineComponent
({
name
:
'
ImagePreview
'
,
props
,
setup
(
props
:
Props
)
{
const
imgState
=
reactive
<
ImgState
>
({
currentUrl
:
''
,
imgScale
:
1
,
imgRotate
:
0
,
imgTop
:
0
,
imgLeft
:
0
,
status
:
StatueEnum
.
LOADING
,
currentIndex
:
0
,
moveX
:
0
,
moveY
:
0
,
show
:
props
.
show
,
});
const
wrapElRef
=
ref
<
HTMLDivElement
|
null
>
(
null
);
const
imgElRef
=
ref
<
HTMLImageElement
|
null
>
(
null
);
// 初始化
function
init
()
{
initMouseWheel
();
const
{
index
,
imageList
}
=
props
;
if
(
!
imageList
||
!
imageList
.
length
)
{
throw
new
Error
(
'
imageList is undefined
'
);
}
imgState
.
currentIndex
=
index
;
handleIChangeImage
(
imageList
[
index
]);
}
// 重置
function
initState
()
{
imgState
.
imgScale
=
1
;
imgState
.
imgRotate
=
0
;
imgState
.
imgTop
=
0
;
imgState
.
imgLeft
=
0
;
}
// 初始化鼠标滚轮事件
function
initMouseWheel
()
{
const
wrapEl
=
unref
(
wrapElRef
);
if
(
!
wrapEl
)
{
return
;
}
(
wrapEl
as
any
).
onmousewheel
=
scrollFunc
;
// 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
document
.
body
.
addEventListener
(
'
DOMMouseScroll
'
,
scrollFunc
);
// 禁止火狐浏览器下拖拽图片的默认事件
document
.
ondragstart
=
function
()
{
return
false
;
};
}
// 监听鼠标滚轮
function
scrollFunc
(
e
:
any
)
{
e
=
e
||
window
.
event
;
e
.
delta
=
e
.
wheelDelta
||
-
e
.
detail
;
e
.
preventDefault
();
if
(
e
.
delta
>
0
)
{
// 滑轮向上滚动
scaleFunc
(
0.015
);
}
if
(
e
.
delta
<
0
)
{
// 滑轮向下滚动
scaleFunc
(
-
0.015
);
}
}
// 缩放函数
function
scaleFunc
(
num
:
number
)
{
if
(
imgState
.
imgScale
<=
0.2
&&
num
<
0
)
return
;
imgState
.
imgScale
+=
num
;
}
// 旋转图片
function
rotateFunc
(
deg
:
number
)
{
imgState
.
imgRotate
+=
deg
;
}
// 鼠标事件
function
handleMouseUp
()
{
const
imgEl
=
unref
(
imgElRef
);
if
(
!
imgEl
)
return
;
imgEl
.
onmousemove
=
null
;
}
// 更换图片
function
handleIChangeImage
(
url
:
string
)
{
imgState
.
status
=
StatueEnum
.
LOADING
;
const
img
=
new
Image
();
img
.
src
=
url
;
img
.
onload
=
()
=>
{
imgState
.
currentUrl
=
url
;
imgState
.
status
=
StatueEnum
.
DONE
;
};
img
.
onerror
=
()
=>
{
imgState
.
status
=
StatueEnum
.
FAIL
;
};
}
// 关闭
function
handleClose
(
e
:
MouseEvent
)
{
e
&&
e
.
stopPropagation
();
imgState
.
show
=
false
;
// 移除火狐浏览器下的鼠标滚动事件
document
.
body
.
removeEventListener
(
'
DOMMouseScroll
'
,
scrollFunc
);
// 恢复火狐及Safari浏览器下的图片拖拽
document
.
ondragstart
=
null
;
}
// 图片复原
function
resume
()
{
initState
();
}
// 上一页下一页
function
handleChange
(
direction
:
'
left
'
|
'
right
'
)
{
const
{
currentIndex
}
=
imgState
;
const
{
imageList
}
=
props
;
if
(
direction
===
'
left
'
)
{
imgState
.
currentIndex
--
;
if
(
currentIndex
<=
0
)
{
imgState
.
currentIndex
=
imageList
.
length
-
1
;
}
}
if
(
direction
===
'
right
'
)
{
imgState
.
currentIndex
++
;
if
(
currentIndex
>=
imageList
.
length
-
1
)
{
imgState
.
currentIndex
=
0
;
}
}
handleIChangeImage
(
imageList
[
imgState
.
currentIndex
]);
}
function
handleAddMoveListener
(
e
:
MouseEvent
)
{
e
=
e
||
window
.
event
;
imgState
.
moveX
=
e
.
clientX
;
imgState
.
moveY
=
e
.
clientY
;
const
imgEl
=
unref
(
imgElRef
);
if
(
imgEl
)
{
imgEl
.
onmousemove
=
moveFunc
;
}
}
function
moveFunc
(
e
:
MouseEvent
)
{
e
=
e
||
window
.
event
;
e
.
preventDefault
();
const
movementX
=
e
.
clientX
-
imgState
.
moveX
;
const
movementY
=
e
.
clientY
-
imgState
.
moveY
;
imgState
.
imgLeft
+=
movementX
;
imgState
.
imgTop
+=
movementY
;
imgState
.
moveX
=
e
.
clientX
;
imgState
.
moveY
=
e
.
clientY
;
}
// 获取图片样式
const
getImageStyle
=
computed
(()
=>
{
const
{
imgScale
,
imgRotate
,
imgTop
,
imgLeft
}
=
imgState
;
return
{
transform
:
`scale(
${
imgScale
}
) rotate(
${
imgRotate
}
deg)`
,
marginTop
:
`
${
imgTop
}
px`
,
marginLeft
:
`
${
imgLeft
}
px`
,
};
});
const
getIsMultipleImage
=
computed
(()
=>
{
const
{
imageList
}
=
props
;
return
imageList
.
length
>
1
;
});
watchEffect
(()
=>
{
if
(
props
.
show
)
{
init
();
}
if
(
props
.
imageList
)
{
initState
();
}
});
const
renderClose
=
()
=>
{
return
(
<
div
class
=
{
`
${
prefixCls
}
__close`
}
onClick
=
{
handleClose
}
>
<
CloseOutlined
class
=
{
`
${
prefixCls
}
__close-icon`
}
/
>
<
/div
>
);
};
const
renderIndex
=
()
=>
{
if
(
!
unref
(
getIsMultipleImage
))
{
return
null
;
}
const
{
currentIndex
}
=
imgState
;
const
{
imageList
}
=
props
;
return
(
<
div
class
=
{
`
${
prefixCls
}
__index`
}
>
{
currentIndex
+
1
}
/ {imageList.length
}
<
/div
>
);
};
const
renderController
=
()
=>
{
return
(
<
div
class
=
{
`
${
prefixCls
}
__controller`
}
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{()
=>
scaleFunc
(
-
0.15
)}
>
<
img
src
=
{
unScaleSvg
}
/
>
<
/div
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{()
=>
scaleFunc
(
0.15
)}
>
<
img
src
=
{
scaleSvg
}
/
>
<
/div
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{
resume
}
>
<
img
src
=
{
resumeSvg
}
/
>
<
/div
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{()
=>
rotateFunc
(
-
90
)}
>
<
img
src
=
{
unRotateSvg
}
/
>
<
/div
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{()
=>
rotateFunc
(
90
)}
>
<
img
src
=
{
rotateSvg
}
/
>
<
/div
>
<
/div
>
);
};
const
renderArrow
=
(
direction
:
'
left
'
|
'
right
'
)
=>
{
if
(
!
unref
(
getIsMultipleImage
))
{
return
null
;
}
return
(
<
div
class
=
{[
`
${
prefixCls
}
__arrow`
,
direction
]}
onClick
=
{()
=>
handleChange
(
direction
)}
>
{
direction
===
'
left
'
?
<
LeftOutlined
/>
:
<
RightOutlined
/>
}
<
/div
>
);
};
return
()
=>
{
return
(
imgState
.
show
&&
(
<
div
class
=
{
prefixCls
}
ref
=
{
wrapElRef
}
onMouseup
=
{
handleMouseUp
}
>
<
div
class
=
{
`
${
prefixCls
}
-content`
}
>
{
/*<Spin*/
}
{
/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/
}
{
/* spinning={true}*/
}
{
/* class={[*/
}
{
/* `${prefixCls}-image`,*/
}
{
/* {*/
}
{
/* hidden: imgState.status !== StatueEnum.LOADING,*/
}
{
/* },*/
}
{
/* ]}*/
}
{
/*/>*/
}
<
img
style
=
{
unref
(
getImageStyle
)}
class
=
{[
`
${
prefixCls
}
-image`
,
imgState
.
status
===
StatueEnum
.
DONE
?
''
:
'
hidden
'
,
]}
ref
=
{
imgElRef
}
src
=
{
imgState
.
currentUrl
}
onMousedown
=
{
handleAddMoveListener
}
/
>
{
renderClose
()}
{
renderIndex
()}
{
renderController
()}
{
renderArrow
(
'
left
'
)}
{
renderArrow
(
'
right
'
)}
<
/div
>
<
/div
>
)
);
};
},
});
</
script
>
<
style
lang=
"less"
>
.img-preview {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: @preview-comp-z-index;
background: rgba(0, 0, 0, 0.5);
user-select: none;
&-content {
display: flex;
width: 100%;
height: 100%;
color: @white;
justify-content: center;
align-items: center;
}
&-image {
cursor: pointer;
transition: transform 0.3s;
}
&__close {
position: absolute;
top: -40px;
right: -40px;
width: 80px;
height: 80px;
overflow: hidden;
color: @white;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
transition: all 0.2s;
&-icon {
position: absolute;
top: 46px;
left: 16px;
font-size: 16px;
}
&:hover {
background-color: rgba(0, 0, 0, 0.8);
}
}
&__index {
position: absolute;
bottom: 5%;
left: 50%;
padding: 0 22px;
font-size: 16px;
background: rgba(109, 109, 109, 0.6);
border-radius: 15px;
transform: translateX(-50%);
}
&__controller {
position: absolute;
bottom: 10%;
left: 50%;
display: flex;
width: 260px;
height: 44px;
padding: 0 22px;
margin-left: -139px;
background: rgba(109, 109, 109, 0.6);
border-radius: 22px;
justify-content: center;
&-item {
display: flex;
height: 100%;
padding: 0 9px;
font-size: 24px;
cursor: pointer;
transition: all 0.2s;
&:hover {
transform: scale(1.2);
}
img {
width: 1em;
}
}
}
&__arrow {
position: absolute;
top: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
font-size: 28px;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
transition: all 0.2s;
&:hover {
background-color: rgba(0, 0, 0, 0.8);
}
&.left {
left: 50px;
}
&.right {
right: 50px;
}
}
}
</
style
>
src/components/Preview/src/functional.ts
浏览文件 @
305630e3
import
ImgPreview
from
'
./index
'
;
import
type
{
Options
,
Props
}
from
'
./typing
'
;
import
ImgPreview
from
'
./Functional.vue
'
;
import
{
isClient
}
from
'
/@/utils/is
'
;
import
type
{
Options
,
Props
}
from
'
./types
'
;
import
{
createVNode
,
render
}
from
'
vue
'
;
let
instance
:
any
=
null
;
let
instance
:
ReturnType
<
typeof
createVNode
>
|
null
=
null
;
export
function
createImgPreview
(
options
:
Options
)
{
if
(
!
isClient
)
return
;
const
{
imageList
,
show
=
true
,
index
=
0
}
=
options
;
...
...
src/components/Preview/src/index.less
已删除
100644 → 0
浏览文件 @
3f6920f7
.img-preview {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: @preview-comp-z-index;
background: rgba(0, 0, 0, 0.5);
user-select: none;
&-content {
display: flex;
width: 100%;
height: 100%;
color: @white;
justify-content: center;
align-items: center;
}
&-image {
cursor: pointer;
transition: transform 0.3s;
}
&__close {
position: absolute;
top: -40px;
right: -40px;
width: 80px;
height: 80px;
overflow: hidden;
color: @white;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
transition: all 0.2s;
&-icon {
position: absolute;
top: 46px;
left: 16px;
font-size: 16px;
}
&:hover {
background-color: rgba(0, 0, 0, 0.8);
}
}
&__index {
position: absolute;
bottom: 5%;
left: 50%;
padding: 0 22px;
font-size: 16px;
background: rgba(109, 109, 109, 0.6);
border-radius: 15px;
transform: translateX(-50%);
}
&__controller {
position: absolute;
bottom: 10%;
left: 50%;
display: flex;
width: 260px;
height: 44px;
padding: 0 22px;
margin-left: -139px;
background: rgba(109, 109, 109, 0.6);
border-radius: 22px;
justify-content: center;
&-item {
display: flex;
height: 100%;
padding: 0 9px;
font-size: 24px;
cursor: pointer;
transition: all 0.2s;
&:hover {
transform: scale(1.2);
}
img {
width: 1em;
}
}
}
&__arrow {
position: absolute;
top: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
font-size: 28px;
cursor: pointer;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
transition: all 0.2s;
&:hover {
background-color: rgba(0, 0, 0, 0.8);
}
&.left {
left: 50px;
}
&.right {
right: 50px;
}
}
}
src/components/Preview/src/index.tsx
已删除
100644 → 0
浏览文件 @
3f6920f7
import
'
./index.less
'
;
import
{
defineComponent
,
ref
,
unref
,
computed
,
reactive
,
watchEffect
}
from
'
vue
'
;
// @ts-ignore
import
{
basicProps
}
from
'
./props
'
;
// @ts-ignore
import
{
Props
}
from
'
./types
'
;
import
{
CloseOutlined
,
LeftOutlined
,
RightOutlined
}
from
'
@ant-design/icons-vue
'
;
// import { Spin } from 'ant-design-vue';
import
resumeSvg
from
'
/@/assets/svg/preview/resume.svg
'
;
import
rotateSvg
from
'
/@/assets/svg/preview/p-rotate.svg
'
;
import
scaleSvg
from
'
/@/assets/svg/preview/scale.svg
'
;
import
unScaleSvg
from
'
/@/assets/svg/preview/unscale.svg
'
;
import
unRotateSvg
from
'
/@/assets/svg/preview/unrotate.svg
'
;
enum
StatueEnum
{
LOADING
,
DONE
,
FAIL
,
}
interface
ImgState
{
currentUrl
:
string
;
imgScale
:
number
;
imgRotate
:
number
;
imgTop
:
number
;
imgLeft
:
number
;
currentIndex
:
number
;
status
:
StatueEnum
;
moveX
:
number
;
moveY
:
number
;
show
:
boolean
;
}
const
prefixCls
=
'
img-preview
'
;
export
default
defineComponent
({
name
:
'
ImagePreview
'
,
props
:
basicProps
,
setup
(
props
:
Props
)
{
const
imgState
=
reactive
<
ImgState
>
({
currentUrl
:
''
,
imgScale
:
1
,
imgRotate
:
0
,
imgTop
:
0
,
imgLeft
:
0
,
status
:
StatueEnum
.
LOADING
,
currentIndex
:
0
,
moveX
:
0
,
moveY
:
0
,
show
:
props
.
show
,
});
const
wrapElRef
=
ref
<
HTMLDivElement
|
null
>
(
null
);
const
imgElRef
=
ref
<
HTMLImageElement
|
null
>
(
null
);
// 初始化
function
init
()
{
initMouseWheel
();
const
{
index
,
imageList
}
=
props
;
if
(
!
imageList
||
!
imageList
.
length
)
{
throw
new
Error
(
'
imageList is undefined
'
);
}
imgState
.
currentIndex
=
index
;
handleIChangeImage
(
imageList
[
index
]);
}
// 重置
function
initState
()
{
imgState
.
imgScale
=
1
;
imgState
.
imgRotate
=
0
;
imgState
.
imgTop
=
0
;
imgState
.
imgLeft
=
0
;
}
// 初始化鼠标滚轮事件
function
initMouseWheel
()
{
const
wrapEl
=
unref
(
wrapElRef
);
if
(
!
wrapEl
)
{
return
;
}
(
wrapEl
as
any
).
onmousewheel
=
scrollFunc
;
// 火狐浏览器没有onmousewheel事件,用DOMMouseScroll代替
document
.
body
.
addEventListener
(
'
DOMMouseScroll
'
,
scrollFunc
);
// 禁止火狐浏览器下拖拽图片的默认事件
document
.
ondragstart
=
function
()
{
return
false
;
};
}
// 监听鼠标滚轮
function
scrollFunc
(
e
:
any
)
{
e
=
e
||
window
.
event
;
e
.
delta
=
e
.
wheelDelta
||
-
e
.
detail
;
e
.
preventDefault
();
if
(
e
.
delta
>
0
)
{
// 滑轮向上滚动
scaleFunc
(
0.015
);
}
if
(
e
.
delta
<
0
)
{
// 滑轮向下滚动
scaleFunc
(
-
0.015
);
}
}
// 缩放函数
function
scaleFunc
(
num
:
number
)
{
if
(
imgState
.
imgScale
<=
0.2
&&
num
<
0
)
return
;
imgState
.
imgScale
+=
num
;
}
// 旋转图片
function
rotateFunc
(
deg
:
number
)
{
imgState
.
imgRotate
+=
deg
;
}
// 鼠标事件
function
handleMouseUp
()
{
const
imgEl
=
unref
(
imgElRef
);
if
(
!
imgEl
)
return
;
imgEl
.
onmousemove
=
null
;
}
// 更换图片
function
handleIChangeImage
(
url
:
string
)
{
imgState
.
status
=
StatueEnum
.
LOADING
;
const
img
=
new
Image
();
img
.
src
=
url
;
img
.
onload
=
()
=>
{
imgState
.
currentUrl
=
url
;
imgState
.
status
=
StatueEnum
.
DONE
;
};
img
.
onerror
=
()
=>
{
imgState
.
status
=
StatueEnum
.
FAIL
;
};
}
// 关闭
function
handleClose
(
e
:
MouseEvent
)
{
e
&&
e
.
stopPropagation
();
imgState
.
show
=
false
;
// 移除火狐浏览器下的鼠标滚动事件
document
.
body
.
removeEventListener
(
'
DOMMouseScroll
'
,
scrollFunc
);
// 恢复火狐及Safari浏览器下的图片拖拽
document
.
ondragstart
=
null
;
}
// 图片复原
function
resume
()
{
initState
();
}
// 上一页下一页
function
handleChange
(
direction
:
'
left
'
|
'
right
'
)
{
const
{
currentIndex
}
=
imgState
;
const
{
imageList
}
=
props
;
if
(
direction
===
'
left
'
)
{
imgState
.
currentIndex
--
;
if
(
currentIndex
<=
0
)
{
imgState
.
currentIndex
=
imageList
.
length
-
1
;
}
}
if
(
direction
===
'
right
'
)
{
imgState
.
currentIndex
++
;
if
(
currentIndex
>=
imageList
.
length
-
1
)
{
imgState
.
currentIndex
=
0
;
}
}
handleIChangeImage
(
imageList
[
imgState
.
currentIndex
]);
}
function
handleAddMoveListener
(
e
:
MouseEvent
)
{
e
=
e
||
window
.
event
;
imgState
.
moveX
=
e
.
clientX
;
imgState
.
moveY
=
e
.
clientY
;
const
imgEl
=
unref
(
imgElRef
);
if
(
imgEl
)
{
imgEl
.
onmousemove
=
moveFunc
;
}
}
function
moveFunc
(
e
:
MouseEvent
)
{
e
=
e
||
window
.
event
;
e
.
preventDefault
();
const
movementX
=
e
.
clientX
-
imgState
.
moveX
;
const
movementY
=
e
.
clientY
-
imgState
.
moveY
;
imgState
.
imgLeft
+=
movementX
;
imgState
.
imgTop
+=
movementY
;
imgState
.
moveX
=
e
.
clientX
;
imgState
.
moveY
=
e
.
clientY
;
}
// 获取图片样式
const
getImageStyle
=
computed
(()
=>
{
const
{
imgScale
,
imgRotate
,
imgTop
,
imgLeft
}
=
imgState
;
return
{
transform
:
`scale(
${
imgScale
}
) rotate(
${
imgRotate
}
deg)`
,
marginTop
:
`
${
imgTop
}
px`
,
marginLeft
:
`
${
imgLeft
}
px`
,
};
});
const
getIsMultipleImage
=
computed
(()
=>
{
const
{
imageList
}
=
props
;
return
imageList
.
length
>
1
;
});
watchEffect
(()
=>
{
if
(
props
.
show
)
{
init
();
}
if
(
props
.
imageList
)
{
initState
();
}
});
const
renderClose
=
()
=>
{
return
(
<
div
class
=
{
`
${
prefixCls
}
__close`
}
onClick
=
{
handleClose
}
>
<
CloseOutlined
class
=
{
`
${
prefixCls
}
__close-icon`
}
/>
</
div
>
);
};
const
renderIndex
=
()
=>
{
if
(
!
unref
(
getIsMultipleImage
))
{
return
null
;
}
const
{
currentIndex
}
=
imgState
;
const
{
imageList
}
=
props
;
return
(
<
div
class
=
{
`
${
prefixCls
}
__index`
}
>
{
currentIndex
+
1
}
/
{
imageList
.
length
}
</
div
>
);
};
const
renderController
=
()
=>
{
return
(
<
div
class
=
{
`
${
prefixCls
}
__controller`
}
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{
()
=>
scaleFunc
(
-
0.15
)
}
>
<
img
src
=
{
unScaleSvg
}
/>
</
div
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{
()
=>
scaleFunc
(
0.15
)
}
>
<
img
src
=
{
scaleSvg
}
/>
</
div
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{
resume
}
>
<
img
src
=
{
resumeSvg
}
/>
</
div
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{
()
=>
rotateFunc
(
-
90
)
}
>
<
img
src
=
{
unRotateSvg
}
/>
</
div
>
<
div
class
=
{
`
${
prefixCls
}
__controller-item`
}
onClick
=
{
()
=>
rotateFunc
(
90
)
}
>
<
img
src
=
{
rotateSvg
}
/>
</
div
>
</
div
>
);
};
const
renderArrow
=
(
direction
:
'
left
'
|
'
right
'
)
=>
{
if
(
!
unref
(
getIsMultipleImage
))
{
return
null
;
}
return
(
<
div
class
=
{
[
`
${
prefixCls
}
__arrow`
,
direction
]
}
onClick
=
{
()
=>
handleChange
(
direction
)
}
>
{
direction
===
'
left
'
?
<
LeftOutlined
/>
:
<
RightOutlined
/>
}
</
div
>
);
};
return
()
=>
{
return
(
imgState
.
show
&&
(
<
div
class
=
{
prefixCls
}
ref
=
{
wrapElRef
}
onMouseup
=
{
handleMouseUp
}
>
<
div
class
=
{
`
${
prefixCls
}
-content`
}
>
{
/*<Spin*/
}
{
/* indicator={<LoadingOutlined style="font-size: 24px" spin />}*/
}
{
/* spinning={true}*/
}
{
/* class={[*/
}
{
/* `${prefixCls}-image`,*/
}
{
/* {*/
}
{
/* hidden: imgState.status !== StatueEnum.LOADING,*/
}
{
/* },*/
}
{
/* ]}*/
}
{
/*/>*/
}
<
img
style
=
{
unref
(
getImageStyle
)
}
class
=
{
[
`
${
prefixCls
}
-image`
,
imgState
.
status
===
StatueEnum
.
DONE
?
''
:
'
hidden
'
]
}
ref
=
{
imgElRef
}
src
=
{
imgState
.
currentUrl
}
onMousedown
=
{
handleAddMoveListener
}
/>
{
renderClose
()
}
{
renderIndex
()
}
{
renderController
()
}
{
renderArrow
(
'
left
'
)
}
{
renderArrow
(
'
right
'
)
}
</
div
>
</
div
>
)
);
};
},
});
src/components/Preview/src/props.ts
已删除
100644 → 0
浏览文件 @
3f6920f7
import
{
PropType
}
from
'
vue
'
;
export
const
basicProps
=
{
show
:
{
type
:
Boolean
as
PropType
<
boolean
>
,
default
:
false
,
},
imageList
:
{
type
:
[
Array
]
as
PropType
<
string
[]
>
,
default
:
null
,
},
index
:
{
type
:
Number
as
PropType
<
number
>
,
default
:
0
,
},
};
src/components/Preview/src/typ
es
.ts
→
src/components/Preview/src/typ
ing
.ts
浏览文件 @
305630e3
文件已移动
src/views/demo/feat/img-preview/index.vue
浏览文件 @
305630e3
<
template
>
<PageWrapper
title=
"图片预览示例"
>
<p
@
click=
"openImg"
>
打开图片
</p>
<ImagePreview
:imageList=
"imgList"
/>
<a-button
@
click=
"openImg"
type=
"primary"
>
无预览图
</a-button>
</PageWrapper>
</
template
>
<
script
lang=
"ts"
>
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录