!104 正式发布v2.1.5版本

Merge pull request !104 from dvadmin/dev
pull/105/head v2.1.5
dvadmin 2023-08-21 06:41:37 +00:00 committed by Gitee
commit f6131f8f4a
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
346 changed files with 133747 additions and 1042 deletions

View File

@ -62,6 +62,7 @@ INSTALLED_APPS = [
] ]
MIDDLEWARE = [ MIDDLEWARE = [
"dvadmin.utils.middleware.HealthCheckMiddleware",
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
@ -412,6 +413,7 @@ PLUGINS_URL_PATTERNS = []
# from dvadmin_ak_sk.settings import * # 秘钥管理管理 # from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理 # from dvadmin_tenants.settings import * # 租户管理
# from dvadmin_cloud_storage.settings import * # 云存储 # from dvadmin_cloud_storage.settings import * # 云存储
#from dvadmin_low_code_crud.settings import * # 低代码操作
# ... # ...
# ********** 一键导入插件配置结束 ********** # ********** 一键导入插件配置结束 **********

View File

@ -2,4 +2,5 @@
# python manage.py makemigrations # python manage.py makemigrations
# python manage.py migrate # python manage.py migrate
# python manage.py init -y # python manage.py init -y
gunicorn -c gunicorn_conf.py application.asgi:application #gunicorn -c gunicorn_conf.py application.asgi:application
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 4

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ import logging
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from application import settings from application import settings
from dvadmin.system import signals
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -29,9 +30,9 @@ class Command(BaseCommand):
reset = True reset = True
if isinstance(options.get("n"), list) or isinstance(options.get("N"), list): if isinstance(options.get("n"), list) or isinstance(options.get("N"), list):
reset = False reset = False
signals.pre_init_complete.send(sender=None, msg='开始初始化', data={"reset": reset})
for app in settings.INSTALLED_APPS: for app in settings.INSTALLED_APPS:
signals.detail_init_complete.send(sender=None, msg='初始化中', data={"app": app, "reset": reset})
try: try:
exec( exec(
f""" f"""
@ -50,4 +51,5 @@ main(reset={reset})
) )
except ModuleNotFoundError: except ModuleNotFoundError:
pass pass
signals.post_init_complete.send(sender=None, msg='初始化完成', data={"reset": reset})
print("初始化数据完成!") print("初始化数据完成!")

View File

@ -1,11 +1,13 @@
import hashlib import hashlib
import os import os
from pathlib import PurePath, PureWindowsPath, PurePosixPath from pathlib import PurePosixPath
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core.files.base import File
from django.db import models from django.db import models
from application import dispatch from application import dispatch
from application.settings import BASE_DIR
from dvadmin.utils.models import CoreModel, table_prefix from dvadmin.utils.models import CoreModel, table_prefix
STATUS_CHOICES = ( STATUS_CHOICES = (
@ -129,6 +131,7 @@ class Dept(CoreModel):
null=True, null=True,
blank=True, blank=True,
help_text="上级部门", help_text="上级部门",
db_index=True
) )
@classmethod @classmethod
@ -160,7 +163,7 @@ class Dept(CoreModel):
class Menu(CoreModel): class Menu(CoreModel):
parent = models.ForeignKey( parent = models.ForeignKey(
to="Menu", to="Menu",
on_delete=models.PROTECT, on_delete=models.CASCADE,
verbose_name="上级菜单", verbose_name="上级菜单",
null=True, null=True,
blank=True, blank=True,
@ -181,6 +184,7 @@ class Menu(CoreModel):
component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True, component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True,
help_text="组件名称") help_text="组件名称")
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态") status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
frame_out = models.BooleanField(default=False, blank=True, verbose_name="是否主框架外", help_text="是否主框架外")
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存") cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示", visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
help_text="侧边栏中是否显示") help_text="侧边栏中是否显示")
@ -302,23 +306,60 @@ def media_file_name(instance, filename):
class FileList(CoreModel): class FileList(CoreModel):
name = models.CharField(max_length=200, null=True, blank=True, verbose_name="名称", help_text="名称") name = models.CharField(max_length=200, null=True, blank=True, verbose_name="名称", help_text="名称")
url = models.FileField(upload_to=media_file_name, null=True, blank=True,) url = models.FileField(upload_to=media_file_name, null=True, blank=True, )
file_url = models.CharField(max_length=255, blank=True, verbose_name="文件地址", help_text="文件地址") file_url = models.CharField(max_length=255, blank=True, verbose_name="文件地址", help_text="文件地址")
engine = models.CharField(max_length=100, default='local', blank=True, verbose_name="引擎", help_text="引擎") engine = models.CharField(max_length=100, default='local', blank=True, verbose_name="引擎", help_text="引擎")
mime_type = models.CharField(max_length=100, blank=True, verbose_name="Mime类型", help_text="Mime类型") mime_type = models.CharField(max_length=100, blank=True, verbose_name="Mime类型", help_text="Mime类型")
size = models.CharField(max_length=36, blank=True, verbose_name="文件大小", help_text="文件大小") size = models.BigIntegerField(default=0, blank=True, verbose_name="文件大小", help_text="文件大小")
md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5") md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5")
@classmethod
def save_file(cls, request, file_path, file_name, mime_type):
# 保存到File model中
instance = FileList()
instance.name = file_name
instance.engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
instance.file_url = os.path.join(file_path, file_name)
instance.mime_type = mime_type
instance.creator = request.user
instance.modifier = request.user.id
instance.dept_belong_id = request.user.dept_id
file_backup = dispatch.get_system_config_values("file_storage.file_backup")
file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
if file_backup:
instance.url = os.path.join(file_path.replace('media/', ''), file_name)
if file_engine == 'oss':
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
with open(os.path.join(BASE_DIR, file_path, file_name), 'rb') as file:
file_path = ali_oss_upload(file, file_name=os.path.join(file_path.replace('media/', ''), file_name))
if file_path:
instance.file_url = file_path
else:
raise ValueError("上传失败")
elif file_engine == 'cos':
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
with open(os.path.join(BASE_DIR, file_path, file_name), 'rb') as file:
file_path = tencent_cos_upload(file, file_name=os.path.join(file_path.replace('media/', ''), file_name))
if file_path:
instance.file_url = file_path
else:
raise ValueError("上传失败")
else:
instance.url = os.path.join(file_path.replace('media/', ''), file_name)
instance.save()
return instance
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.md5sum: # file is new if not self.md5sum and self.url: # file is new
md5 = hashlib.md5() md5 = hashlib.md5()
for chunk in self.url.chunks(): for chunk in self.url.chunks():
md5.update(chunk) md5.update(chunk)
self.md5sum = md5.hexdigest() self.md5sum = md5.hexdigest()
if not self.size: if not self.size and self.url:
self.size = self.url.size self.size = self.url.size
if not self.file_url: if not self.file_url:
url = media_file_name(self,self.name) url = media_file_name(self, self.name)
self.file_url = f'media/{url}' self.file_url = f'media/{url}'
super(FileList, self).save(*args, **kwargs) super(FileList, self).save(*args, **kwargs)

View File

@ -0,0 +1,12 @@
from django.dispatch import Signal
# 初始化信号
pre_init_complete = Signal(providing_args=['msg', 'data'])
detail_init_complete = Signal(providing_args=['msg', 'data'])
post_init_complete = Signal(providing_args=['msg', 'data'])
# 租户初始化信号
pre_tenants_init_complete = Signal(providing_args=['msg', 'data'])
detail_tenants_init_complete = Signal(providing_args=['msg', 'data'])
post_tenants_init_complete = Signal(providing_args=['msg', 'data'])
post_tenants_all_init_complete = Signal(providing_args=['msg', 'data'])
# 租户创建完成信号
tenants_create_complete = Signal(providing_args=['msg', 'data'])

View File

@ -215,38 +215,39 @@ class DataVViewSet(GenericViewSet):
CHINA_PROVINCES = [ CHINA_PROVINCES = [
{'name': '北京', 'code': '110000'}, {'name': '北京', 'code': '110000'},
{'name': '天津', 'code': '120000'}, {'name': '天津', 'code': '120000'},
{'name': '河北', 'code': '130000'}, {'name': '河北', 'code': '130000'},
{'name': '山西', 'code': '140000'}, {'name': '山西', 'code': '140000'},
{'name': '内蒙古', 'code': '150000'}, {'name': '内蒙古', 'code': '150000'},
{'name': '辽宁', 'code': '210000'}, {'name': '辽宁', 'code': '210000'},
{'name': '吉林', 'code': '220000'}, {'name': '吉林', 'code': '220000'},
{'name': '黑龙江', 'code': '230000'}, {'name': '黑龙江', 'code': '230000'},
{'name': '上海', 'code': '310000'}, {'name': '上海', 'code': '310000'},
{'name': '江苏', 'code': '320000'}, {'name': '江苏', 'code': '320000'},
{'name': '浙江', 'code': '330000'}, {'name': '浙江', 'code': '330000'},
{'name': '安徽', 'code': '340000'}, {'name': '安徽', 'code': '340000'},
{'name': '福建', 'code': '350000'}, {'name': '福建', 'code': '350000'},
{'name': '江西', 'code': '360000'}, {'name': '江西', 'code': '360000'},
{'name': '山东', 'code': '370000'}, {'name': '山东', 'code': '370000'},
{'name': '河南', 'code': '410000'}, {'name': '河南', 'code': '410000'},
{'name': '湖北', 'code': '420000'}, {'name': '湖北', 'code': '420000'},
{'name': '湖南', 'code': '430000'}, {'name': '湖南', 'code': '430000'},
{'name': '广东', 'code': '440000'}, {'name': '广东', 'code': '440000'},
{'name': '广西', 'code': '450000'}, {'name': '广西', 'code': '450000'},
{'name': '海南', 'code': '460000'}, {'name': '海南', 'code': '460000'},
{'name': '重庆', 'code': '500000'}, {'name': '重庆', 'code': '500000'},
{'name': '四川', 'code': '510000'}, {'name': '四川', 'code': '510000'},
{'name': '贵州', 'code': '520000'}, {'name': '贵州', 'code': '520000'},
{'name': '云南', 'code': '530000'}, {'name': '云南', 'code': '530000'},
{'name': '西藏', 'code': '540000'}, {'name': '西藏', 'code': '540000'},
{'name': '陕西', 'code': '610000'}, {'name': '陕西', 'code': '610000'},
{'name': '甘肃', 'code': '620000'}, {'name': '甘肃', 'code': '620000'},
{'name': '青海', 'code': '630000'}, {'name': '青海', 'code': '630000'},
{'name': '宁夏', 'code': '640000'}, {'name': '宁夏', 'code': '640000'},
{'name': '新疆', 'code': '650000'}, {'name': '新疆', 'code': '650000'},
{'name': '台湾', 'code': '710000'}, {'name': '台湾', 'code': '710000'},
{'name': '香港', 'code': '810000'}, {'name': '香港', 'code': '810000'},
{'name': '澳门', 'code': '820000'}, {'name': '澳门', 'code': '820000'},
{'name': '钓鱼岛', 'code': '900000'},
{'name': '未知区域', 'code': '000000'}, {'name': '未知区域', 'code': '000000'},
] ]
provinces = [x['name'] for x in CHINA_PROVINCES] provinces = [x['name'] for x in CHINA_PROVINCES]

View File

@ -13,6 +13,7 @@ from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.permission import AnonymousUserPermission from dvadmin.utils.permission import AnonymousUserPermission
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.utils.filters import LazyLoadFilter
class DeptSerializer(CustomModelSerializer): class DeptSerializer(CustomModelSerializer):
@ -120,6 +121,12 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
fields = '__all__' fields = '__all__'
class DeptLazyFilter(LazyLoadFilter):
class Meta:
model = Dept
fields = ['name', 'parent', 'status']
class DeptViewSet(CustomModelViewSet): class DeptViewSet(CustomModelViewSet):
""" """
部门管理接口 部门管理接口
@ -129,11 +136,13 @@ class DeptViewSet(CustomModelViewSet):
retrieve:单例 retrieve:单例
destroy:删除 destroy:删除
""" """
queryset = Dept.objects.all() queryset = Dept.objects.all()
serializer_class = DeptSerializer serializer_class = DeptSerializer
create_serializer_class = DeptCreateUpdateSerializer create_serializer_class = DeptCreateUpdateSerializer
update_serializer_class = DeptCreateUpdateSerializer update_serializer_class = DeptCreateUpdateSerializer
filter_fields = ['name', 'id', 'parent'] # filter_fields = ["name", "id", "parent"]
filter_class = DeptLazyFilter
search_fields = [] search_fields = []
# extra_filter_backends = [] # extra_filter_backends = []
import_serializer_class = DeptImportSerializer import_serializer_class = DeptImportSerializer

View File

@ -1,11 +1,22 @@
import base64
import datetime
import hashlib import hashlib
import mimetypes import json
import os
import random
from pathlib import PurePosixPath
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import action
from application import dispatch from application.settings import BASE_DIR
from dvadmin.system.models import FileList from application import dispatch, settings
from dvadmin.system.models import FileList, media_file_name
from dvadmin.system.views.ueditor_settings import ueditor_upload_settings, ueditor_settings
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.string_util import format_bytes
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@ -13,7 +24,16 @@ class FileSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True) url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance): def get_url(self, instance):
# return 'media/' + str(instance.url) if self.request.query_params.get('prefix'):
if settings.ENVIRONMENT in ['local']:
prefix = 'http://127.0.0.1:8000'
elif settings.ENVIRONMENT in ['test']:
prefix = 'http://{host}/api'.format(host=self.request.get_host())
else:
prefix = 'https://{host}/api'.format(host=self.request.get_host())
if instance.file_url:
return instance.file_url if instance.file_url.startswith('http') else f"{prefix}/{instance.file_url}"
return (f'{prefix}/media/{str(instance.url)}')
return instance.file_url or (f'media/{str(instance.url)}') return instance.file_url or (f'media/{str(instance.url)}')
class Meta: class Meta:
@ -21,8 +41,8 @@ class FileSerializer(CustomModelSerializer):
fields = "__all__" fields = "__all__"
def create(self, validated_data): def create(self, validated_data):
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local' file_engine = dispatch.get_system_config_values("file_storage.file_engine") or 'local'
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup") file_backup = dispatch.get_system_config_values("file_storage.file_backup")
file = self.initial_data.get('file') file = self.initial_data.get('file')
file_size = file.size file_size = file.size
validated_data['name'] = file.name validated_data['name'] = file.name
@ -37,14 +57,18 @@ class FileSerializer(CustomModelSerializer):
validated_data['url'] = file validated_data['url'] = file
if file_engine == 'oss': if file_engine == 'oss':
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
file_path = ali_oss_upload(file) h = validated_data['md5sum']
basename, ext = os.path.splitext(file.name)
file_path = ali_oss_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower()))
if file_path: if file_path:
validated_data['file_url'] = file_path validated_data['file_url'] = file_path
else: else:
raise ValueError("上传失败") raise ValueError("上传失败")
elif file_engine == 'cos': elif file_engine == 'cos':
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
file_path = tencent_cos_upload(file) h = validated_data['md5sum']
basename, ext = os.path.splitext(file.name)
file_path = tencent_cos_upload(file, file_name=PurePosixPath("files", h[:1], h[1:2], h + ext.lower()))
if file_path: if file_path:
validated_data['file_url'] = file_path validated_data['file_url'] = file_path
else: else:
@ -75,3 +99,162 @@ class FileViewSet(CustomModelViewSet):
serializer_class = FileSerializer serializer_class = FileSerializer
filter_fields = ['name', ] filter_fields = ['name', ]
permission_classes = [] permission_classes = []
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, request=request)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return DetailResponse(data=serializer.data, msg="新增成功")
@csrf_exempt
@action(methods=["GET", "POST"], detail=False, permission_classes=[])
def ueditor(self, request):
action = self.request.query_params.get("action", "")
reponse_action = {
"config": self.get_ueditor_settings,
"uploadimage": self.upload_file,
"uploadscrawl": self.upload_file,
"uploadvideo": self.upload_file,
"uploadfile": self.upload_file,
}
return reponse_action[action](request)
def get_ueditor_settings(self, request):
return HttpResponse(json.dumps(ueditor_upload_settings, ensure_ascii=False),
content_type="application/javascript")
# 保存上传的文件
def save_upload_file(self, file, file_path):
with open(file_path, 'wb') as f:
try:
for chunk in file.chunks():
f.write(chunk)
except Exception as e:
return f"写入文件错误:{e}"
return u"SUCCESS"
def get_path_format_vars(self):
return {
"year": datetime.datetime.now().strftime("%Y"),
"month": datetime.datetime.now().strftime("%m"),
"day": datetime.datetime.now().strftime("%d"),
"date": datetime.datetime.now().strftime("%Y%m%d"),
"time": datetime.datetime.now().strftime("%H%M%S"),
"datetime": datetime.datetime.now().strftime("%Y%m%d%H%M%S"),
"rnd": random.randrange(100, 999)
}
def get_output_path(self, path_format_var):
"""
取得输出文件的路径
:param path_format_var:
:return:
"""
file_name = (ueditor_settings["defaultPathFormat"] % path_format_var).replace("\\", "/")
# 分解OutputPathFormat
output_path = os.path.join('media', 'ueditor', f'{self.request.user.id}')
if not os.path.exists(output_path):
os.makedirs(output_path)
return (file_name, output_path)
# 涂鸦功能上传处理
def save_scrawl_file(self, request, file_path, file_name):
import base64
instance = None
try:
content = request.data.get(ueditor_upload_settings.get("scrawlFieldName", "upfile"))
f = open(os.path.join(BASE_DIR, file_path, file_name), 'wb')
f.write(base64.b64decode(content))
f.close()
state = "SUCCESS"
instance = FileList.save_file(request, file_path, file_name, mime_type='image/png')
except Exception as e:
state = f"写入图片文件错误:{e}"
return state, instance
def upload_file(self, request):
"""上传文件"""
state = "SUCCESS"
action = self.request.query_params.get("action")
# 上传文件
upload_field_name_list = {
"uploadfile": "fileFieldName",
"uploadimage": "imageFieldName",
"uploadscrawl": "scrawlFieldName",
"catchimage": "catcherFieldName",
"uploadvideo": "videoFieldName",
}
upload_field_name = self.request.query_params.get(upload_field_name_list[action],
ueditor_upload_settings.get(action, "upfile"))
# 上传涂鸦涂鸦是采用base64编码上传的需要单独处理
if action == "uploadscrawl":
upload_file_name = "scrawl.png"
upload_file_size = 0
else:
# 取得上传的文件
file = request.FILES.get(upload_field_name, None)
if file is None:
return HttpResponse(json.dumps(u"{'state:'ERROR'}"), content_type="application/javascript")
upload_file_name = file.name
upload_file_size = file.size
# 取得上传的文件的原始名称
upload_original_name, upload_original_ext = os.path.splitext(upload_file_name)
# 文件类型检验
upload_allow_type = {
"uploadfile": "fileAllowFiles",
"uploadimage": "imageAllowFiles",
"uploadvideo": "videoAllowFiles"
}
if action in upload_allow_type:
allow_type = list(self.request.query_params.get(upload_allow_type[action],
ueditor_upload_settings.get(upload_allow_type[action], "")))
if not upload_original_ext.lower() in allow_type:
state = u"服务器不允许上传%s类型的文件。" % upload_original_ext
return HttpResponse({"state": state}, content_type="application/javascript")
# 大小检验
upload_max_size = {
"uploadfile": "filwMaxSize",
"uploadimage": "imageMaxSize",
"uploadscrawl": "scrawlMaxSize",
"uploadvideo": "videoMaxSize"
}
max_size = int(self.request.query_params.get(upload_max_size[action],
ueditor_upload_settings.get(upload_max_size[action], 0)))
if max_size != 0:
if upload_file_size > max_size:
state = u"上传文件大小不允许超过%s" % format_bytes(max_size)
return HttpResponse({"state": state}, content_type="application/javascript")
path_format_var = self.get_path_format_vars()
path_format_var.update({
"basename": upload_original_name,
"extname": upload_original_ext[1:],
"filename": upload_file_name,
})
# 取得输出文件的路径
format_file_name, output_path = self.get_output_path(path_format_var)
# 所有检测完成后写入文件
file_instance = None
if state == "SUCCESS":
if action == "uploadscrawl":
state, file_instance = self.save_scrawl_file(request, file_path=output_path,
file_name=format_file_name)
else:
file = request.FILES.get(upload_field_name, None)
# 保存到文件中如果保存错误需要返回ERROR
state = self.save_upload_file(file, os.path.join(BASE_DIR, output_path, format_file_name))
# 保存到附件管理中
file_instance = FileList.save_file(request, output_path, format_file_name, mime_type=file.content_type)
# 返回数据
return_info = {
'url': file_instance.file_url if file_instance else os.path.join(output_path, format_file_name), # 保存后的文件名称
'original': upload_file_name, # 原始文件名
'type': upload_original_ext,
'state': state, # 上传状态成功时返回SUCCESS,其他任何值将原样返回至图片上传框中
'size': upload_file_size
}
return HttpResponse(json.dumps(return_info, ensure_ascii=False), content_type="application/javascript")

View File

@ -114,7 +114,7 @@ class MenuInitSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Menu model = Menu
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status', fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id'] 'cache', 'visible', 'parent', 'children', 'menu_button', 'frame_out', 'creator', 'dept_belong_id']
extra_kwargs = { extra_kwargs = {
'creator': {'write_only': True}, 'creator': {'write_only': True},
'dept_belong_id': {'write_only': True} 'dept_belong_id': {'write_only': True}
@ -137,7 +137,8 @@ class WebRouterSerializer(CustomModelSerializer):
else: else:
# 根据当前角色获取权限按钮id集合 # 根据当前角色获取权限按钮id集合
permissionIds = self.request.user.role.values_list('permission', flat=True) permissionIds = self.request.user.role.values_list('permission', flat=True)
queryset = instance.menuPermission.filter(id__in=permissionIds, menu=instance.id).values_list('value', flat=True) queryset = instance.menuPermission.filter(id__in=permissionIds, menu=instance.id).values_list('value',
flat=True)
if queryset: if queryset:
return queryset return queryset
else: else:
@ -145,8 +146,9 @@ class WebRouterSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Menu model = Menu
fields = ('id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component', fields = (
'component_name', 'cache', 'visible', 'menuPermission') 'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
'component_name', 'cache', 'visible', 'menuPermission', 'frame_out')
read_only_fields = ["id"] read_only_fields = ["id"]
@ -165,6 +167,7 @@ class MenuViewSet(CustomModelViewSet):
update_serializer_class = MenuCreateSerializer update_serializer_class = MenuCreateSerializer
search_fields = ['name', 'status'] search_fields = ['name', 'status']
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog'] filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
# extra_filter_backends = [] # extra_filter_backends = []
@action(methods=['GET'], detail=False, permission_classes=[]) @action(methods=['GET'], detail=False, permission_classes=[])
@ -179,7 +182,7 @@ class MenuViewSet(CustomModelViewSet):
data = serializer.data data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功") return SuccessResponse(data=data, total=len(data), msg="获取成功")
def list(self,request): def list(self, request):
"""懒加载""" """懒加载"""
params = request.query_params params = request.query_params
parent = params.get('parent', None) parent = params.get('parent', None)

View File

@ -6,7 +6,12 @@
@Created on: 2021/6/3 003 0:30 @Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理 @Remark: 菜单按钮管理
""" """
from dvadmin.system.models import MenuButton from django.db.models import F, CharField, Value, ExpressionWrapper
from django.db.models.functions import Cast, Concat
from rest_framework.decorators import action
from dvadmin.system.models import MenuButton, Menu
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@ -18,7 +23,7 @@ class MenuButtonSerializer(CustomModelSerializer):
class Meta: class Meta:
model = MenuButton model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method', 'menu'] fields = ["id", "name", "value", "api", "method", "menu"]
read_only_fields = ["id"] read_only_fields = ["id"]
@ -29,7 +34,7 @@ class MenuButtonInitSerializer(CustomModelSerializer):
class Meta: class Meta:
model = MenuButton model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method', 'menu'] fields = ["id", "name", "value", "api", "method", "menu"]
read_only_fields = ["id"] read_only_fields = ["id"]
@ -53,8 +58,26 @@ class MenuButtonViewSet(CustomModelViewSet):
retrieve:单例 retrieve:单例
destroy:删除 destroy:删除
""" """
queryset = MenuButton.objects.all() queryset = MenuButton.objects.all()
serializer_class = MenuButtonSerializer serializer_class = MenuButtonSerializer
create_serializer_class = MenuButtonCreateUpdateSerializer create_serializer_class = MenuButtonCreateUpdateSerializer
update_serializer_class = MenuButtonCreateUpdateSerializer update_serializer_class = MenuButtonCreateUpdateSerializer
extra_filter_backends = [] extra_filter_backends = []
@action(methods=["GET"], detail=False, permission_classes=[])
def get_btn_permission(self, request):
"""
获取当前用户的按钮权限
"""
user = request.user
if not user.is_superuser:
menuIds = user.role.values_list("menu__id", flat=True)
else:
menuIds = Menu.objects.filter(status=1)
queryset = (
MenuButton.objects.filter(menu__in=menuIds)
.annotate(permission=Concat("menu__web_path", Value(":"), "value", output_field=CharField()))
.values_list("permission", flat=True)
)
return DetailResponse(data=queryset)

View File

@ -7,6 +7,7 @@
@Remark: 系统配置 @Remark: 系统配置
""" """
import django_filters import django_filters
from django.db import connection
from django.db.models import Q from django.db.models import Q
from django_filters.rest_framework import BooleanFilter from django_filters.rest_framework import BooleanFilter
from rest_framework import serializers from rest_framework import serializers
@ -276,5 +277,6 @@ class InitSettingsViewSet(APIView):
SystemConfig.objects.filter(status=False, parent_id__isnull=False).values('parent__key', SystemConfig.objects.filter(status=False, parent_id__isnull=False).values('parent__key',
'key')] 'key')]
data = dict(filter(lambda x: x[0] not in backend_config, data.items())) data = dict(filter(lambda x: x[0] not in backend_config, data.items()))
data = self.filter_system_config_values(data=data) if hasattr(connection, 'tenant'):
data['schema_name'] = connection.tenant.schema_name
return DetailResponse(data=data) return DetailResponse(data=data)

View File

@ -0,0 +1,116 @@
from django.conf import settings as gSettings # 全局设置
# 工具栏样式,可以添加任意多的模式
toolbars_settings = {
"besttome": [
['source', 'undo', 'redo', 'bold', 'italic', 'underline', 'forecolor', 'backcolor', 'superscript', 'subscript',
"justifyleft", "justifycenter", "justifyright", "insertorderedlist", "insertunorderedlist", "blockquote",
'formatmatch', "removeformat", 'autotypeset', 'inserttable', "pasteplain", "wordimage", "searchreplace", "map",
"preview", "fullscreen"],
['insertcode', 'paragraph', "fontfamily", "fontsize", 'link', 'unlink', 'insertimage', 'insertvideo',
'attachment', 'emotion', "date", "time"]],
"mini": [['source', '|', 'undo', 'redo', '|', 'bold', 'italic', 'underline', 'formatmatch', 'autotypeset', '|',
'forecolor', 'backcolor', '|', 'link', 'unlink', '|', 'simpleupload', 'attachment']],
"normal": [['source', '|', 'undo', 'redo', '|', 'bold', 'italic', 'underline', 'removeformat', 'formatmatch',
'autotypeset', '|', 'forecolor', 'backcolor', '|', 'link', 'unlink', '|', 'simpleupload', 'emotion',
'attachment', '|', 'inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow',
'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows',
'splittocols']]
}
# 默认的Ueditor设置请参见ueditor.config.js
ueditor_settings = {
"toolbars": toolbars_settings["normal"],
"autoFloatEnabled": False,
"defaultPathFormat": "%(basename)s_%(datetime)s_%(rnd)s.%(extname)s" # 默认保存上传文件的命名方式
}
# 请参阅php文件夹里面的config.json进行配置
ueditor_upload_settings = {
# 上传图片配置项
"imageActionName": "uploadimage", # 执行上传图片的action名称
"imageMaxSize": 10485760, # 上传大小限制单位B,10M
"imageFieldName": "upfile", # * 提交的图片表单名称 */
"imagePathFormat": "",
"imageInsertAlign": "none",
"imageAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], # 上传图片格式显示
# 涂鸦图片上传配置项 */
"scrawlActionName": "uploadscrawl", # 执行上传涂鸦的action名称 */
"scrawlFieldName": "upfile", # 提交的图片表单名称 */
"scrawlMaxSize": 10485760, # 上传大小限制单位B 10M
"scrawlPathFormat": "",
"scrawlInsertAlign": "none",
# 截图工具上传 */
"snapscreenActionName": "uploadimage", # 执行上传截图的action名称 */
"snapscreenPathFormat": "",
"snapscreenInsertAlign": "none", # /* 插入的图片浮动方式 */
# 抓取远程图片配置 */
"catcherLocalDomain": ["127.0.0.1", "localhost", "img.baidu.com"],
"catcherPathFormat": "",
"catcherActionName": "catchimage", # 执行抓取远程图片的action名称 */
"catcherFieldName": "source", # 提交的图片列表表单名称 */
"catcherMaxSize": 10485760, # 上传大小限制单位B */
"catcherAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], # 抓取图片格式显示 */
"catcherInsertAlign": "none", # /* 插入的图片浮动方式 */
# 上传视频配置 */
"videoActionName": "uploadvideo", # 执行上传视频的action名称 */
"videoPathFormat": "",
"videoFieldName": "upfile", # 提交的视频表单名称 */
"videoMaxSize": 102400000, # 上传大小限制单位B默认100MB */
"videoAllowFiles": [
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid"], # 上传视频格式显示 */
# 上传文件配置 */
"fileActionName": "uploadfile", # controller里,执行上传视频的action名称 */
"filePathFormat": "",
"fileFieldName": "upfile", # 提交的文件表单名称 */
"fileMaxSize": 204800000, # 上传大小限制单位B200MB */
"fileAllowFiles": [
".png", ".jpg", ".jpeg", ".gif", ".bmp",
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
], # 上传文件格式显示 */
# 列出指定目录下的图片 */
"imageManagerActionName": "listimage", # 执行图片管理的action名称 */
"imageManagerListPath": "",
"imageManagerListSize": 30, # 每次列出文件数量 */
"imageManagerAllowFiles": [".png", ".jpg", ".jpeg", ".gif", ".bmp"], # 列出的文件类型 */
# 列出指定目录下的文件 */
"fileManagerActionName": "listfile", # 执行文件管理的action名称 */
"fileManagerListPath": "",
"fileManagerListSize": 30, # 每次列出文件数量 */
"fileManagerAllowFiles": [
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".tif", ".psd"
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg",
".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml",
".exe", ".com", ".dll", ".msi"
] # 列出的文件类型 */
}
# 更新配置从用户配置文件settings.py重新读入配置UEDITOR_SETTINGS,覆盖默认
def update_user_settings():
user_settings = getattr(gSettings, "UEDITOR_SETTINGS", {}).copy()
if 'config' in user_settings:
ueditor_settings.update(user_settings["config"])
if 'upload' in user_settings:
ueditor_upload_settings.update(user_settings["upload"])
# 读取用户Settings文件并覆盖默认配置
update_user_settings()
# 取得配置项参数
def get_ueditor_settings(key, default=None):
return ueditor_settings.get(key, default)

View File

@ -249,16 +249,16 @@ class UserViewSet(CustomModelViewSet):
serializer_class = UserSerializer serializer_class = UserSerializer
create_serializer_class = UserCreateSerializer create_serializer_class = UserCreateSerializer
update_serializer_class = UserUpdateSerializer update_serializer_class = UserUpdateSerializer
# filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"] filter_fields = ["^name", "~username", "^mobile", "is_active", "dept", "user_type", "$dept__name"]
filter_fields = { # filter_fields = {
"name": ["icontains"], # "name": ["icontains"],
"mobile": ["icontains"], # "mobile": ["iregex"],
"username": ["icontains"], # "username": ["icontains"],
"gender": ["icontains"], # "is_active": ["icontains"],
"is_active": ["icontains"], # "dept": ["exact"],
"dept": ["exact"], # "user_type": ["exact"],
"user_type": ["exact"], # "dept__name": ["icontains"],
} # }
search_fields = ["username", "name", "gender", "dept__name", "role__name"] search_fields = ["username", "name", "gender", "dept__name", "role__name"]
# 导出 # 导出
export_field_label = { export_field_label = {

View File

@ -3,10 +3,12 @@ import json
import os import os
from django.apps import apps from django.apps import apps
from django.db import connection
from rest_framework import request from rest_framework import request
from application import settings from application import settings
from dvadmin.system.models import Users from application.dispatch import is_tenants_mode
from dvadmin.system.models import Users, Menu
class CoreInitialize: class CoreInitialize:
@ -30,11 +32,14 @@ class CoreInitialize:
def init_base(self, Serializer, unique_fields=None): def init_base(self, Serializer, unique_fields=None):
model = Serializer.Meta.model model = Serializer.Meta.model
if is_tenants_mode() and connection.tenant.schema_name !='public' and model._meta.model_name == 'menu':
# 超级租户模式下,取消初始化菜单
return
path_file = os.path.join(apps.get_app_config(self.app.split('.')[-1]).path, 'fixtures', path_file = os.path.join(apps.get_app_config(self.app.split('.')[-1]).path, 'fixtures',
f'init_{Serializer.Meta.model._meta.model_name}.json') f'init_{Serializer.Meta.model._meta.model_name}.json')
if not os.path.isfile(path_file): if not os.path.isfile(path_file):
return return
with open(path_file,encoding="utf-8") as f: with open(path_file, encoding="utf-8") as f:
for data in json.load(f): for data in json.load(f):
filter_data = {} filter_data = {}
# 配置过滤条件,如果有唯一标识字段则使用唯一标识字段,否则使用全部字段 # 配置过滤条件,如果有唯一标识字段则使用唯一标识字段,否则使用全部字段

View File

@ -9,9 +9,9 @@
import logging import logging
import traceback import traceback
from django.db.models import ProtectedError from django.db.models import ProtectedError, RestrictedError
from django.http import Http404 from django.http import Http404
from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed, PermissionDenied
from rest_framework.status import HTTP_407_PROXY_AUTHENTICATION_REQUIRED, HTTP_401_UNAUTHORIZED from rest_framework.status import HTTP_407_PROXY_AUTHENTICATION_REQUIRED, HTTP_401_UNAUTHORIZED
from rest_framework.views import set_rollback, exception_handler from rest_framework.views import set_rollback, exception_handler
@ -29,32 +29,34 @@ def CustomExceptionHandler(ex, context):
:param context: :param context:
:return: :return:
""" """
msg = '' msg = ""
code = 4000 code = 4000
# 调用默认的异常处理函数 # 调用默认的异常处理函数
response = exception_handler(ex, context) response = exception_handler(ex, context)
if isinstance(ex, AuthenticationFailed): if isinstance(ex, AuthenticationFailed):
# 如果是身份验证错误 # 如果是身份验证错误
if response and response.data.get('detail') =="Given token not valid for any token type": if response and response.data.get("detail") == "Given token not valid for any token type":
code = 401 code = 401
msg = ex.detail msg = ex.detail
elif response and response.data.get('detail') =="Token is blacklisted": elif response and response.data.get("detail") == "Token is blacklisted":
# token在黑名单 # token在黑名单
return ErrorResponse(status=HTTP_401_UNAUTHORIZED) return ErrorResponse(status=HTTP_401_UNAUTHORIZED)
else: else:
code = 401 code = 401
msg = ex.detail msg = ex.detail
elif isinstance(ex,Http404): elif isinstance(ex, Http404):
code = 400 code = 400
msg = "接口地址不正确" msg = "接口地址不正确"
elif isinstance(ex, DRFAPIException): elif isinstance(ex, DRFAPIException):
set_rollback() set_rollback()
msg = ex.detail msg = ex.detail
if isinstance(msg,dict): if isinstance(ex, PermissionDenied):
msg = f'{msg} ({context["request"].method}: {context["request"].path})'
if isinstance(msg, dict):
for k, v in msg.items(): for k, v in msg.items():
for i in v: for i in v:
msg = "%s:%s" % (k, i) msg = "%s:%s" % (k, i)
elif isinstance(ex, ProtectedError): elif isinstance(ex, (ProtectedError, RestrictedError)):
set_rollback() set_rollback()
msg = "无法删除:该条数据与其他数据有相关绑定" msg = "无法删除:该条数据与其他数据有相关绑定"
# elif isinstance(ex, DatabaseError): # elif isinstance(ex, DatabaseError):

View File

@ -12,10 +12,15 @@ from collections import OrderedDict
from functools import reduce from functools import reduce
import six import six
from django import forms
from django.db import models
from django.db.models import Q, F from django.db.models import Q, F
from django.db.models.constants import LOOKUP_SEP from django.db.models.constants import LOOKUP_SEP
from django_filters import utils from django_filters import utils
from django_filters.filters import CharFilter from django_filters.conf import settings
from django_filters.constants import ALL_FIELDS
from django_filters.filters import CharFilter, BooleanFilter
from django_filters.filterset import FilterSet, FilterSetMetaclass
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field from django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend from rest_framework.filters import BaseFilterBackend
@ -68,9 +73,7 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
permission__api=F("url"), permission__method=F("method") permission__api=F("url"), permission__method=F("method")
) )
api_white_list = [ api_white_list = [
str(item.get("permission__api").replace("{id}", ".*?")) str(item.get("permission__api").replace("{id}", ".*?")) + ":" + str(item.get("permission__method"))
+ ":"
+ str(item.get("permission__method"))
for item in api_white_list for item in api_white_list
if item.get("permission__api") if item.get("permission__api")
] ]
@ -116,19 +119,13 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据) # 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
if 0 in dataScope_list: if 0 in dataScope_list:
return queryset.filter( return queryset.filter(creator=request.user, dept_belong_id=user_dept_id)
creator=request.user, dept_belong_id=user_dept_id
)
# 5. 自定数据权限 获取部门,根据部门过滤 # 5. 自定数据权限 获取部门,根据部门过滤
dept_list = [] dept_list = []
for ele in dataScope_list: for ele in dataScope_list:
if ele == 4: if ele == 4:
dept_list.extend( dept_list.extend(request.user.role.filter(status=1).values_list("dept__id", flat=True))
request.user.role.filter(status=1).values_list(
"dept__id", flat=True
)
)
elif ele == 2: elif ele == 2:
dept_list.append(user_dept_id) dept_list.append(user_dept_id)
elif ele == 1: elif ele == 1:
@ -138,7 +135,7 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
user_dept_id, user_dept_id,
) )
) )
if queryset.model._meta.model_name == 'dept': if queryset.model._meta.model_name == "dept":
return queryset.filter(id__in=list(set(dept_list))) return queryset.filter(id__in=list(set(dept_list)))
return queryset.filter(dept_belong_id__in=list(set(dept_list))) return queryset.filter(dept_belong_id__in=list(set(dept_list)))
else: else:
@ -153,6 +150,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
"$": "iregex", "$": "iregex",
"~": "icontains", "~": "icontains",
} }
filter_fields = "__all__"
def construct_search(self, field_name, lookup_expr=None): def construct_search(self, field_name, lookup_expr=None):
lookup = self.lookup_prefixes.get(field_name[0]) lookup = self.lookup_prefixes.get(field_name[0])
@ -160,14 +158,16 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
field_name = field_name[1:] field_name = field_name[1:]
else: else:
lookup = lookup_expr lookup = lookup_expr
if field_name.endswith(lookup): if lookup:
return field_name if field_name.endswith(lookup):
return LOOKUP_SEP.join([field_name, lookup]) return field_name
return LOOKUP_SEP.join([field_name, lookup])
return field_name
def find_filter_lookups(self, orm_lookups, search_term_key): def find_filter_lookups(self, orm_lookups, search_term_key):
for lookup in orm_lookups: for lookup in orm_lookups:
# if lookup.find(search_term_key) >= 0: # if lookup.find(search_term_key) >= 0:
new_lookup = lookup.split("__")[0] new_lookup = LOOKUP_SEP.join(lookup.split(LOOKUP_SEP)[:-1]) if len(lookup.split(LOOKUP_SEP)) > 1 else lookup
# 修复条件搜索错误 bug # 修复条件搜索错误 bug
if new_lookup == search_term_key: if new_lookup == search_term_key:
return lookup return lookup
@ -183,18 +183,22 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# TODO: remove assertion in 2.1 # TODO: remove assertion in 2.1
if filterset_class is None and hasattr(view, "filter_class"): if filterset_class is None and hasattr(view, "filter_class"):
utils.deprecate( utils.deprecate(
"`%s.filter_class` attribute should be renamed `filterset_class`." "`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__
% view.__class__.__name__
) )
filterset_class = getattr(view, "filter_class", None) filterset_class = getattr(view, "filter_class", None)
# TODO: remove assertion in 2.1 # TODO: remove assertion in 2.1
if filterset_fields is None and hasattr(view, "filter_fields"): if filterset_fields is None and hasattr(view, "filter_fields"):
utils.deprecate( utils.deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`." "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
% view.__class__.__name__
) )
filterset_fields = getattr(view, "filter_fields", None) self.filter_fields = getattr(view, "filter_fields", None)
if isinstance(self.filter_fields, (list, tuple)):
filterset_fields = [
field[1:] if field[0] in self.lookup_prefixes.keys() else field for field in self.filter_fields
]
else:
filterset_fields = self.filter_fields
if filterset_class: if filterset_class:
filterset_model = filterset_class._meta.model filterset_model = filterset_class._meta.model
@ -214,6 +218,51 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
MetaBase = getattr(self.filterset_base, "Meta", object) MetaBase = getattr(self.filterset_base, "Meta", object)
class AutoFilterSet(self.filterset_base): class AutoFilterSet(self.filterset_base):
@classmethod
def get_all_model_fields(cls, model):
opts = model._meta
return [
f.name
for f in sorted(opts.fields + opts.many_to_many)
if (f.name == "id")
or not isinstance(f, models.AutoField)
and not (getattr(f.remote_field, "parent_link", False))
]
@classmethod
def get_fields(cls):
"""
Resolve the 'fields' argument that should be used for generating filters on the
filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'.
"""
model = cls._meta.model
fields = cls._meta.fields
exclude = cls._meta.exclude
assert not (fields is None and exclude is None), (
"Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' "
"has been deprecated since 0.15.0 and is now disallowed. Add an explicit "
"'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__
)
# Setting exclude with no fields implies all other fields.
if exclude is not None and fields is None:
fields = ALL_FIELDS
# Resolve ALL_FIELDS into all fields for the filterset's model.
if fields == ALL_FIELDS:
fields = cls.get_all_model_fields(model)
# Remove excluded fields
exclude = exclude or []
if not isinstance(fields, dict):
fields = [(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude]
else:
fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude]
return OrderedDict(fields)
@classmethod @classmethod
def get_filters(cls): def get_filters(cls):
""" """
@ -242,9 +291,12 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
if field is None: if field is None:
undefined.append(field_name) undefined.append(field_name)
# 更新默认字符串搜索为模糊搜索 # 更新默认字符串搜索为模糊搜索
if isinstance(field, (models.CharField)) and filterset_fields == '__all__' and lookups == [ if (
'exact']: isinstance(field, (models.CharField))
lookups = ['icontains'] and filterset_fields == "__all__"
and lookups == ["exact"]
):
lookups = ["icontains"]
for lookup_expr in lookups: for lookup_expr in lookups:
filter_name = cls.get_filter_name(field_name, lookup_expr) filter_name = cls.get_filter_name(field_name, lookup_expr)
@ -254,20 +306,15 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
continue continue
if field is not None: if field is not None:
filters[filter_name] = cls.filter_for_field( filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)
field, field_name, lookup_expr
)
# Allow Meta.fields to contain declared filters *only* when a list/tuple # Allow Meta.fields to contain declared filters *only* when a list/tuple
if isinstance(cls._meta.fields, (list, tuple)): if isinstance(cls._meta.fields, (list, tuple)):
undefined = [ undefined = [f for f in undefined if f not in cls.declared_filters]
f for f in undefined if f not in cls.declared_filters
]
if undefined: if undefined:
raise TypeError( raise TypeError(
"'Meta.fields' must not contain non-model field names: %s" "'Meta.fields' must not contain non-model field names: %s" % ", ".join(undefined)
% ", ".join(undefined)
) )
# Add in declared filters. This is necessary since we don't enforce adding # Add in declared filters. This is necessary since we don't enforce adding
@ -289,22 +336,31 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
return queryset return queryset
if filterset.__class__.__name__ == "AutoFilterSet": if filterset.__class__.__name__ == "AutoFilterSet":
queryset = filterset.queryset queryset = filterset.queryset
orm_lookups = [] filter_fields = filterset.filters if self.filter_fields == "__all__" else self.filter_fields
for search_field in filterset.filters: orm_lookup_dict = dict(
if isinstance(filterset.filters[search_field], CharFilter): zip(
orm_lookups.append( [field for field in filter_fields],
self.construct_search(six.text_type(search_field), filterset.filters[search_field].lookup_expr) [filterset.filters[lookup].lookup_expr for lookup in filterset.filters.keys()],
) )
else: )
orm_lookups.append(search_field) orm_lookups = [
self.construct_search(lookup, lookup_expr) for lookup, lookup_expr in orm_lookup_dict.items()
]
# print(orm_lookups)
conditions = [] conditions = []
queries = [] queries = []
for search_term_key in filterset.data.keys(): for search_term_key in filterset.data.keys():
orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key) orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key)
if not orm_lookup: if not orm_lookup or filterset.data.get(search_term_key) == '':
continue continue
query = Q(**{orm_lookup: filterset.data[search_term_key]}) filterset_data_len = len(filterset.data.getlist(search_term_key))
queries.append(query) if filterset_data_len == 1:
query = Q(**{orm_lookup: filterset.data[search_term_key]})
queries.append(query)
elif filterset_data_len == 2:
orm_lookup += '__range'
query = Q(**{orm_lookup: filterset.data.getlist(search_term_key)})
queries.append(query)
if len(queries) > 0: if len(queries) > 0:
conditions.append(reduce(operator.and_, queries)) conditions.append(reduce(operator.and_, queries))
queryset = queryset.filter(reduce(operator.and_, conditions)) queryset = queryset.filter(reduce(operator.and_, conditions))
@ -315,3 +371,146 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
if not filterset.is_valid() and self.raise_exception: if not filterset.is_valid() and self.raise_exception:
raise utils.translate_validation(filterset.errors) raise utils.translate_validation(filterset.errors)
return filterset.qs return filterset.qs
# ####################### 懒加载FilterSet ####################### #
import time
def calculate_execution_time(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f"Function {func.__name__} took {execution_time:.6f} seconds to execute.", flush=True)
return result
return wrapper
# def get_children(model: models, obj_id: int, all_qs=None, rec_list=None):
# if not all_qs:
# all_qs = model.objects.all().values("id", "parent")
# if rec_list is None:
# rec_list = [obj_id]
# for ele in all_qs:
# if ele.get("parent") == obj_id:
# rec_list.append(ele.get("id"))
# get_dept(ele.get("id"), all_qs, rec_list)
# return list(set(rec_list))
# @calculate_execution_time
# def get_qs_children(model, qs):
# dept_ids = []
# for d in qs:
# r = get_children(model, d.id)
# dept_ids.extend(r)
# return list(set(dept_ids))
def next_layer_data(qs_filter, qs_node):
parent_nodes = set(qs_node.values_list("id", flat=True))
if set(qs_filter) == set(qs_node):
return parent_nodes
# qs_filter内所有父级id 去重
parent_ids = set()
for node in qs_filter:
while node.parent:
if node.id in parent_nodes:
parent_ids.add(node.id)
break
if node.parent.id in parent_nodes:
parent_ids.add(node.parent.id)
break
node = node.parent
# print(f"过滤查询集 ==> {qs_filter}", flush=True)
# print(f"待渲染节点的id ==> {parent_nodes=}", flush=True)
# print(f"过滤查询集的父节点id ==> {parent_ids=}", flush=True)
return parent_ids
def construct_data(qs_filter, qs_node, is_parent):
filter_node_ids = set(qs_filter.values_list("id", flat=True))
render_node_ids = set(qs_node.values_list("id", flat=True))
hidden_node_ids = set()
for node in qs_filter:
while node.parent:
if node.parent in qs_filter:
hidden_node_ids.add(node.id)
node = node.parent
on_show = filter_node_ids.difference(hidden_node_ids)
on_expand = hidden_node_ids & render_node_ids
# print(f"完整查询结果 {filter_node_ids}")
# print(f"待展示的节点(未过滤) {render_node_ids}")
# print(f"查询结果中的子节点 {hidden_node_ids}")
# print(f"查询后首先渲染的父节点 {on_show}")
# print(f"展开父节点时要渲染的节点 {on_expand}")
return on_expand if is_parent else on_show
class FilterSetOptions:
def __init__(self, options=None):
self.model = getattr(options, "model", None)
self.fields = getattr(options, "fields", None)
self.exclude = getattr(options, "exclude", None)
# CharField默认模糊查询
self.filter_overrides = getattr(
options,
"filter_overrides",
{
models.CharField: {
"filter_class": CharFilter,
"extra": lambda f: {
"lookup_expr": "icontains",
},
},
models.BooleanField: {
"filter_class": BooleanFilter,
"extra": lambda f: {
"widget": forms.RadioSelect,
},
},
},
)
self.form = getattr(options, "form", forms.Form)
class LazyLoadFilterSetMetaclass(FilterSetMetaclass):
def __new__(cls, name, bases, attrs):
attrs["declared_filters"] = cls.get_declared_filters(bases, attrs)
new_class = super().__new__(cls, name, bases, attrs)
new_class._meta = FilterSetOptions(getattr(new_class, "Meta", None))
new_class.base_filters = new_class.get_filters()
return new_class
class LazyLoadFilter(FilterSet, metaclass=LazyLoadFilterSetMetaclass):
@property
# @calculate_execution_time
def qs(self):
queryset = self.queryset
# print(self.form.cleaned_data, flush=True)
filter_params = [k for k, v in self.form.cleaned_data.items() if v in [None, ""]]
for field in filter_params:
self.form.cleaned_data.pop(field)
is_parent = self.form.cleaned_data.pop("parent", None) is not None
# print(queryset, flush=True)
if self.form.cleaned_data:
self.queryset = queryset.model.objects.all()
# 从根节点开始
# node_ids = next_layer_data(super().qs, queryset)
# 按匹配结果显示
node_ids = construct_data(super().qs, queryset, is_parent)
return queryset.model.objects.filter(id__in=node_ids)
return super().qs

View File

@ -2,9 +2,11 @@
日志 django中间件 日志 django中间件
""" """
import json import json
import logging
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
from django.http import HttpResponse, HttpResponseServerError
from django.utils.deprecation import MiddlewareMixin from django.utils.deprecation import MiddlewareMixin
from dvadmin.system.models import OperationLog from dvadmin.system.models import OperationLog
@ -87,3 +89,58 @@ class ApiLoggingMiddleware(MiddlewareMixin):
if self.methods == 'ALL' or request.method in self.methods: if self.methods == 'ALL' or request.method in self.methods:
self.__handle_response(request, response) self.__handle_response(request, response)
return response return response
logger = logging.getLogger("healthz")
class HealthCheckMiddleware(object):
"""
存活检查中间件
"""
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
if request.method == "GET":
if request.path == "/readiness":
return self.readiness(request)
elif request.path == "/healthz":
return self.healthz(request)
return self.get_response(request)
def healthz(self, request):
"""
Returns that the server is alive.
"""
return HttpResponse("OK")
def readiness(self, request):
# Connect to each database and do a generic standard SQL query
# that doesn't write any data and doesn't depend on any tables
# being present.
try:
from django.db import connections
for name in connections:
cursor = connections[name].cursor()
cursor.execute("SELECT 1;")
row = cursor.fetchone()
if row is None:
return HttpResponseServerError("db: invalid response")
except Exception as e:
logger.exception(e)
return HttpResponseServerError("db: cannot connect to database.")
# Call get_stats() to connect to each memcached instance and get it's stats.
# This can effectively check if each is online.
try:
from django.core.cache import caches
from django.core.cache.backends.memcached import BaseMemcachedCache
for cache in caches.all():
if isinstance(cache, BaseMemcachedCache):
stats = cache._cache.get_stats()
if len(stats) != len(cache._servers):
return HttpResponseServerError("cache: cannot connect to cache.")
except Exception as e:
logger.exception(e)
return HttpResponseServerError("cache: cannot connect to cache.")
return HttpResponse("OK")

View File

@ -10,8 +10,8 @@ import uuid
from datetime import date, timedelta from datetime import date, timedelta
from django.apps import apps from django.apps import apps
from django.db import models, connection, ProgrammingError from django.db import models, transaction, connection, ProgrammingError
from django.db.models import QuerySet from django.core.exceptions import ObjectDoesNotExist
from application import settings from application import settings
from application.dispatch import is_tenants_mode from application.dispatch import is_tenants_mode
@ -19,10 +19,16 @@ from application.dispatch import is_tenants_mode
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀 table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
class SoftDeleteQuerySet(QuerySet): class SoftDeleteQuerySet(models.query.QuerySet):
pass @transaction.atomic
def delete(self, cascade=True):
if cascade: # delete one by one if cascade
for obj in self.all():
obj.delete(cascade=cascade)
return self.update(is_deleted=True)
def hard_delete(self):
return super().delete()
class SoftDeleteManager(models.Manager): class SoftDeleteManager(models.Manager):
@ -34,8 +40,8 @@ class SoftDeleteManager(models.Manager):
def filter(self, *args, **kwargs): def filter(self, *args, **kwargs):
# 考虑是否主动传入is_deleted # 考虑是否主动传入is_deleted
if not kwargs.get('is_deleted') is None: if not kwargs.get("is_deleted") is None:
self.__add_is_del_filter = True self.__add_is_del_filter = kwargs.get("is_deleted")
return super(SoftDeleteManager, self).filter(*args, **kwargs) return super(SoftDeleteManager, self).filter(*args, **kwargs)
def get_queryset(self): def get_queryset(self):
@ -49,8 +55,10 @@ class SoftDeleteManager(models.Manager):
def get_month_range(start_day, end_day): def get_month_range(start_day, end_day):
months = (end_day.year - start_day.year) * 12 + end_day.month - start_day.month months = (end_day.year - start_day.year) * 12 + end_day.month - start_day.month
month_range = ['%s-%s-01' % (start_day.year + mon // 12, str(mon % 12 + 1).zfill(2)) month_range = [
for mon in range(start_day.month - 1, start_day.month + months)] "%s-%s-01" % (start_day.year + mon // 12, str(mon % 12 + 1).zfill(2))
for mon in range(start_day.month - 1, start_day.month + months)
]
return month_range return month_range
@ -59,20 +67,100 @@ class SoftDeleteModel(models.Model):
软删除模型 软删除模型
一旦继承,就将开启软删除 一旦继承,就将开启软删除
""" """
is_deleted = models.BooleanField(verbose_name="是否软删除", help_text='是否软删除', default=False, db_index=True)
is_deleted = models.BooleanField(verbose_name="是否软删除", help_text="是否软删除", default=False, db_index=True)
objects = SoftDeleteManager() objects = SoftDeleteManager()
class Meta: class Meta:
abstract = True abstract = True
verbose_name = '软删除模型' verbose_name = "软删除模型"
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
def delete(self, using=None, soft_delete=True, *args, **kwargs): @transaction.atomic
def delete(self, using=None, cascade=True, *args, **kwargs):
""" """
重写删除方法,直接开启软删除 重写删除方法,直接开启软删除
""" """
self.is_deleted = True self.is_deleted = True
self.save(using=using) self.save(using=using)
if cascade:
self.delete_related_objects(raise_exception=True)
# raise Exception("delete_related_objects")
def hard_delete(self):
return super().delete()
soft_delete_kwargs = {
"related_names": [],
}
@classmethod
def _get_kwargs(cls):
return cls.soft_delete_kwargs
@classmethod
def _get_relations(cls):
relations = {"foreign": [], "self": []}
related_fields = cls._get_kwargs().get("related_names", [])
if not related_fields:
fields = cls._meta.get_fields(include_hidden=True)
mutated_fields = [field for field in fields if field.is_relation and hasattr(field, "related_name")]
m2m_models = [field.through for field in mutated_fields if field.many_to_many]
related_fields = [
field.related_name
for field in mutated_fields
if not field.many_to_many and field.related_model not in m2m_models and field.related_name
]
tree_model_field = [
field.field.name
for field in mutated_fields
if not field.many_to_many and field.related_model is field.model
]
relations["self"] = f"{tree_model_field[0]}_id" if len(tree_model_field) == 1 else None
relations["foreign"] = related_fields
return relations
def _is_cascade(self, relation):
on_delete_case = self._meta.get_field(relation).on_delete.__name__
return on_delete_case == "CASCADE"
def _get_related_objects(self, relation):
qs = getattr(self, relation)
if isinstance(qs, models.Manager):
return qs
return
def related_objects(self, raise_exception=False, use_soft_manager=False):
relations = self._get_relations()
objects = {}
for relation in relations["foreign"]:
try:
qs = self._get_related_objects(relation)
except ObjectDoesNotExist as e:
if raise_exception:
raise e
continue
else:
objects[relation] = qs
if relations["self"]:
objects["self"] = self.__class__.objects.filter(**{relations["self"]: self.id})
print(f"related_objects: {objects}", flush=True)
return objects
def delete_related_objects(self, raise_exception=False):
for relation, qs in self.related_objects(raise_exception=raise_exception).items():
if relation == "self":
qs.delete()
continue
if self._is_cascade(relation):
print(f"model {self.__class__} : cascade delete {relation} objects {qs.all()}", flush=True)
qs.all().delete()
else:
print(f"model {self.__class__} : protect delete {relation} objects {qs.all()}", flush=True)
if qs.all().exists():
self.hard_delete()
qs.all().hard_delete()
# raise Exception("xxxxxxxxxxx for test xxxxxxxxxxx")
class CoreModel(models.Model): class CoreModel(models.Model):

View File

@ -1,11 +1,3 @@
# -*- coding: utf-8 -*-
"""
@author: 猿小天
@contact: QQ:1638245306
@Created on: 2021/6/6 006 10:30
@Remark: 自定义权限
"""
import re import re
from django.contrib.auth.models import AnonymousUser from django.contrib.auth.models import AnonymousUser
@ -95,3 +87,32 @@ class CustomPermission(BasePermission):
return True return True
else: else:
return False return False
class SuperuserPermission(BasePermission):
"""
超级管理员权限类
"""
def has_permission(self, request, view):
if isinstance(request.user, AnonymousUser):
return False
# 判断是否是超级管理员
if request.user.is_superuser:
return True
class AdminPermission(BasePermission):
"""
普通管理员权限类
"""
def has_permission(self, request, view):
if isinstance(request.user, AnonymousUser):
return False
# 判断是否是超级管理员
is_superuser = request.user.is_superuser
# 判断是否是管理员角色
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
return True

View File

@ -3,7 +3,7 @@ certifi==2021.5.30
chardet==4.0.0 chardet==4.0.0
coreapi==2.3.3 coreapi==2.3.3
coreschema==0.0.4 coreschema==0.0.4
Django==3.2.12 Django==3.2.19
django-comment-migrate==0.1.7 django-comment-migrate==0.1.7
django-cors-headers==3.10.1 django-cors-headers==3.10.1
django-filter==22.1 django-filter==22.1
@ -11,8 +11,8 @@ django-ranged-response==0.2.0
django-redis==5.2.0 django-redis==5.2.0
django-restql==0.15.3 django-restql==0.15.3
django-simple-captcha==0.5.17 django-simple-captcha==0.5.17
django-tenants==3.4.8 django-tenants==3.5.0
django-timezone-field==4.2.3 django-timezone-field==5.0
djangorestframework==3.14.0 djangorestframework==3.14.0
djangorestframework-simplejwt==5.2.2 djangorestframework-simplejwt==5.2.2
packaging==23.0 packaging==23.0
@ -29,7 +29,7 @@ pyparsing==2.4.7
pyPEG2==2.15.2 pyPEG2==2.15.2
pypinyin==0.48.0 pypinyin==0.48.0
pytz==2021.1 pytz==2021.1
requests==2.28.2 requests==2.28.0
ruamel.yaml==0.17.10 ruamel.yaml==0.17.10
ruamel.yaml.clib==0.2.4 ruamel.yaml.clib==0.2.4
six==1.16.0 six==1.16.0

View File

@ -4,6 +4,6 @@ COPY web/. .
RUN npm install --registry=https://registry.npm.taobao.org RUN npm install --registry=https://registry.npm.taobao.org
RUN npm run build RUN npm run build
FROM nginx:alpine FROM registry.cn-zhangjiakou.aliyuncs.com/dvadmin-pro/nginx:alpine
COPY ./docker_env/nginx/my.conf /etc/nginx/conf.d/my.conf COPY ./docker_env/nginx/my.conf /etc/nginx/conf.d/my.conf
COPY --from=0 /web/dist /usr/share/nginx/html COPY --from=0 /web/dist /usr/share/nginx/html

View File

@ -6,7 +6,6 @@ VUE_APP_TITLE=DvAdmin
# 网络请求公用地址 # 网络请求公用地址
VUE_APP_API=/api/ VUE_APP_API=/api/
# 仓库地址 # 仓库地址
VUE_APP_REPO=https://github.com/d2-projects/d2-admin-start-kit VUE_APP_REPO=https://github.com/d2-projects/d2-admin-start-kit

View File

@ -6,4 +6,6 @@ VUE_APP_TITLE=企业级后台管理系统
VUE_APP_PM_ENABLED = true VUE_APP_PM_ENABLED = true
# 后端接口地址及端口(域名) # 后端接口地址及端口(域名)
VUE_APP_API = "http://127.0.0.1:8000" VUE_APP_API = "http://127.0.0.1:8000"
VUE_APP_VERSION = '2.1.5'
# 文件存储引擎
VUE_APP_FILE_ENGINE = 'local' # oss、cos、local

View File

@ -13,3 +13,5 @@ VUE_APP_SCOURCE_LINK=FALSE
VUE_APP_PUBLIC_PATH=/ VUE_APP_PUBLIC_PATH=/
# 启用权限管理 # 启用权限管理
VUE_APP_PM_ENABLED = true VUE_APP_PM_ENABLED = true
# 文件存储引擎
VUE_APP_FILE_ENGINE = 'local' # oss、cos、local

View File

@ -13,3 +13,5 @@ VUE_APP_SCOURCE_LINK=FALSE
VUE_APP_PUBLIC_PATH=/ VUE_APP_PUBLIC_PATH=/
# 启用权限管理 # 启用权限管理
VUE_APP_PM_ENABLED = true VUE_APP_PM_ENABLED = true
# 文件存储引擎
VUE_APP_FILE_ENGINE = 'local' # oss、cos、local

View File

@ -1,6 +1,6 @@
{ {
"name": "django-vue-admin", "name": "django-vue-admin",
"version": "2.1.4", "version": "2.1.5",
"scripts": { "scripts": {
"serve": "vue-cli-service serve --open", "serve": "vue-cli-service serve --open",
"start": "npm run serve", "start": "npm run serve",
@ -19,6 +19,7 @@
"china-division": "^2.4.0", "china-division": "^2.4.0",
"core-js": "^3.4.3", "core-js": "^3.4.3",
"cropperjs": "^1.5.6", "cropperjs": "^1.5.6",
"crypto-js": "^4.1.1",
"d2-crud-plus": "^2.17.9", "d2-crud-plus": "^2.17.9",
"d2-crud-x": "^2.17.9", "d2-crud-x": "^2.17.9",
"d2p-extends": "^2.17.9", "d2p-extends": "^2.17.9",
@ -29,6 +30,7 @@
"faker": "^4.1.0", "faker": "^4.1.0",
"flex.css": "^1.1.7", "flex.css": "^1.1.7",
"fuse.js": "^5.2.3", "fuse.js": "^5.2.3",
"highlight.js": "^11.8.0",
"hotkeys-js": "^3.7.3", "hotkeys-js": "^3.7.3",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"js-md5": "^0.7.3", "js-md5": "^0.7.3",
@ -42,6 +44,9 @@
"ua-parser-js": "^0.7.20", "ua-parser-js": "^0.7.20",
"viser-vue": "^2.4.8", "viser-vue": "^2.4.8",
"vue": "2.7.14", "vue": "2.7.14",
"vue-clipboard2": "^0.3.3",
"vue-core-video-player": "^0.2.0",
"vue-cropper": "^0.6.2",
"vue-echarts": "^6.5.4", "vue-echarts": "^6.5.4",
"vue-grid-layout": "^2.4.0", "vue-grid-layout": "^2.4.0",
"vue-html2pdf": "^1.8.0", "vue-html2pdf": "^1.8.0",
@ -49,6 +54,7 @@
"vue-infinite-scroll": "^2.0.2", "vue-infinite-scroll": "^2.0.2",
"vue-router": "^3.6.5", "vue-router": "^3.6.5",
"vue-splitpane": "^1.0.6", "vue-splitpane": "^1.0.6",
"vue-swiper-component": "^2.1.3",
"vuex": "^3.1.2", "vuex": "^3.1.2",
"vxe-table": "^3.3.2", "vxe-table": "^3.3.2",
"xe-utils": "^3.2.1" "xe-utils": "^3.2.1"

View File

@ -0,0 +1,40 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<style type="text/css">
*{color: #838383;margin: 0;padding: 0}
html,body {font-size: 12px;overflow: hidden; }
.content{padding:5px 0 0 15px;}
input{width:210px;height:21px;line-height:21px;margin-left: 4px;}
</style>
</head>
<body>
<div class="content">
<span><var id="lang_input_anchorName"></var></span><input id="anchorName" value="" />
</div>
<script type="text/javascript" src="../internal.js"></script>
<script type="text/javascript">
var anchorInput = $G('anchorName'),
node = editor.selection.getRange().getClosedNode();
if(node && node.tagName == 'IMG' && (node = node.getAttribute('anchorname'))){
anchorInput.value = node;
}
anchorInput.onkeydown = function(evt){
evt = evt || window.event;
if(evt.keyCode == 13){
editor.execCommand('anchor', anchorInput.value);
dialog.close();
domUtils.preventDefault(evt)
}
};
dialog.onok = function (){
editor.execCommand('anchor', anchorInput.value);
dialog.close();
};
$focus(anchorInput);
</script>
</body>
</html>

View File

@ -0,0 +1,681 @@
@charset "utf-8";
/* dialog样式 */
.wrapper {
zoom: 1;
width: 630px;
*width: 626px;
height: 380px;
margin: 0 auto;
padding: 10px;
position: relative;
font-family: sans-serif;
}
/*tab样式框大小*/
.tabhead {
float:left;
}
.tabbody {
width: 100%;
height: 346px;
position: relative;
clear: both;
}
.tabbody .panel {
position: absolute;
width: 0;
height: 0;
background: #fff;
overflow: hidden;
display: none;
}
.tabbody .panel.focus {
width: 100%;
height: 346px;
display: block;
}
/* 上传附件 */
.tabbody #upload.panel {
width: 0;
height: 0;
overflow: hidden;
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
background: #fff;
display: block;
}
.tabbody #upload.panel.focus {
width: 100%;
height: 346px;
display: block;
clip: auto;
}
#upload .queueList {
margin: 0;
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
}
#upload p {
margin: 0;
}
.element-invisible {
width: 0 !important;
height: 0 !important;
border: 0;
padding: 0;
margin: 0;
overflow: hidden;
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
}
#upload .placeholder {
margin: 10px;
border: 2px dashed #e6e6e6;
*border: 0px dashed #e6e6e6;
height: 172px;
padding-top: 150px;
text-align: center;
background: url(./images/image.png) center 70px no-repeat;
color: #cccccc;
font-size: 18px;
position: relative;
top:0;
*top: 10px;
}
#upload .placeholder .webuploader-pick {
font-size: 18px;
background: #00b7ee;
border-radius: 3px;
line-height: 44px;
padding: 0 30px;
*width: 120px;
color: #fff;
display: inline-block;
margin: 0 auto 20px auto;
cursor: pointer;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
#upload .placeholder .webuploader-pick-hover {
background: #00a2d4;
}
#filePickerContainer {
text-align: center;
}
#upload .placeholder .flashTip {
color: #666666;
font-size: 12px;
position: absolute;
width: 100%;
text-align: center;
bottom: 20px;
}
#upload .placeholder .flashTip a {
color: #0785d1;
text-decoration: none;
}
#upload .placeholder .flashTip a:hover {
text-decoration: underline;
}
#upload .placeholder.webuploader-dnd-over {
border-color: #999999;
}
#upload .filelist {
list-style: none;
margin: 0;
padding: 0;
overflow-x: hidden;
overflow-y: auto;
position: relative;
height: 300px;
}
#upload .filelist:after {
content: '';
display: block;
width: 0;
height: 0;
overflow: hidden;
clear: both;
}
#upload .filelist li {
width: 113px;
height: 113px;
background: url(./images/bg.png);
text-align: center;
margin: 9px 0 0 9px;
*margin: 6px 0 0 6px;
position: relative;
display: block;
float: left;
overflow: hidden;
font-size: 12px;
}
#upload .filelist li p.log {
position: relative;
top: -45px;
}
#upload .filelist li p.title {
position: absolute;
top: 0;
left: 0;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
top: 5px;
text-indent: 5px;
text-align: left;
}
#upload .filelist li p.progress {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
height: 8px;
overflow: hidden;
z-index: 50;
margin: 0;
border-radius: 0;
background: none;
-webkit-box-shadow: 0 0 0;
}
#upload .filelist li p.progress span {
display: none;
overflow: hidden;
width: 0;
height: 100%;
background: #1483d8 url(./images/progress.png) repeat-x;
-webit-transition: width 200ms linear;
-moz-transition: width 200ms linear;
-o-transition: width 200ms linear;
-ms-transition: width 200ms linear;
transition: width 200ms linear;
-webkit-animation: progressmove 2s linear infinite;
-moz-animation: progressmove 2s linear infinite;
-o-animation: progressmove 2s linear infinite;
-ms-animation: progressmove 2s linear infinite;
animation: progressmove 2s linear infinite;
-webkit-transform: translateZ(0);
}
@-webkit-keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
@-moz-keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
@keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
#upload .filelist li p.imgWrap {
position: relative;
z-index: 2;
line-height: 113px;
vertical-align: middle;
overflow: hidden;
width: 113px;
height: 113px;
-webkit-transform-origin: 50% 50%;
-moz-transform-origin: 50% 50%;
-o-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
-webit-transition: 200ms ease-out;
-moz-transition: 200ms ease-out;
-o-transition: 200ms ease-out;
-ms-transition: 200ms ease-out;
transition: 200ms ease-out;
}
#upload .filelist li p.imgWrap.notimage {
margin-top: 0;
width: 111px;
height: 111px;
border: 1px #eeeeee solid;
}
#upload .filelist li p.imgWrap.notimage i.file-preview {
margin-top: 15px;
}
#upload .filelist li img {
width: 100%;
}
#upload .filelist li p.error {
background: #f43838;
color: #fff;
position: absolute;
bottom: 0;
left: 0;
height: 28px;
line-height: 28px;
width: 100%;
z-index: 100;
display:none;
}
#upload .filelist li .success {
display: block;
position: absolute;
left: 0;
bottom: 0;
height: 40px;
width: 100%;
z-index: 200;
background: url(./images/success.png) no-repeat right bottom;
background-image: url(./images/success.gif) \9;
}
#upload .filelist li.filePickerBlock {
width: 113px;
height: 113px;
background: url(./images/image.png) no-repeat center 12px;
border: 1px solid #eeeeee;
border-radius: 0;
}
#upload .filelist li.filePickerBlock div.webuploader-pick {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
opacity: 0;
background: none;
font-size: 0;
}
#upload .filelist div.file-panel {
position: absolute;
height: 0;
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \0;
background: rgba(0, 0, 0, 0.5);
width: 100%;
top: 0;
left: 0;
overflow: hidden;
z-index: 300;
}
#upload .filelist div.file-panel span {
width: 24px;
height: 24px;
display: inline;
float: right;
text-indent: -9999px;
overflow: hidden;
background: url(./images/icons.png) no-repeat;
background: url(./images/icons.gif) no-repeat \9;
margin: 5px 1px 1px;
cursor: pointer;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#upload .filelist div.file-panel span.rotateLeft {
display:none;
background-position: 0 -24px;
}
#upload .filelist div.file-panel span.rotateLeft:hover {
background-position: 0 0;
}
#upload .filelist div.file-panel span.rotateRight {
display:none;
background-position: -24px -24px;
}
#upload .filelist div.file-panel span.rotateRight:hover {
background-position: -24px 0;
}
#upload .filelist div.file-panel span.cancel {
background-position: -48px -24px;
}
#upload .filelist div.file-panel span.cancel:hover {
background-position: -48px 0;
}
#upload .statusBar {
height: 45px;
border-bottom: 1px solid #dadada;
margin: 0 10px;
padding: 0;
line-height: 45px;
vertical-align: middle;
position: relative;
}
#upload .statusBar .progress {
border: 1px solid #1483d8;
width: 198px;
background: #fff;
height: 18px;
position: absolute;
top: 12px;
display: none;
text-align: center;
line-height: 18px;
color: #6dbfff;
margin: 0 10px 0 0;
}
#upload .statusBar .progress span.percentage {
width: 0;
height: 100%;
left: 0;
top: 0;
background: #1483d8;
position: absolute;
}
#upload .statusBar .progress span.text {
position: relative;
z-index: 10;
}
#upload .statusBar .info {
display: inline-block;
font-size: 14px;
color: #666666;
}
#upload .statusBar .btns {
position: absolute;
top: 7px;
right: 0;
line-height: 30px;
}
#filePickerBtn {
display: inline-block;
float: left;
}
#upload .statusBar .btns .webuploader-pick,
#upload .statusBar .btns .uploadBtn,
#upload .statusBar .btns .uploadBtn.state-uploading,
#upload .statusBar .btns .uploadBtn.state-paused {
background: #ffffff;
border: 1px solid #cfcfcf;
color: #565656;
padding: 0 18px;
display: inline-block;
border-radius: 3px;
margin-left: 10px;
cursor: pointer;
font-size: 14px;
float: left;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#upload .statusBar .btns .webuploader-pick-hover,
#upload .statusBar .btns .uploadBtn:hover,
#upload .statusBar .btns .uploadBtn.state-uploading:hover,
#upload .statusBar .btns .uploadBtn.state-paused:hover {
background: #f0f0f0;
}
#upload .statusBar .btns .uploadBtn,
#upload .statusBar .btns .uploadBtn.state-paused{
background: #00b7ee;
color: #fff;
border-color: transparent;
}
#upload .statusBar .btns .uploadBtn:hover,
#upload .statusBar .btns .uploadBtn.state-paused:hover{
background: #00a2d4;
}
#upload .statusBar .btns .uploadBtn.disabled {
pointer-events: none;
filter:alpha(opacity=60);
-moz-opacity:0.6;
-khtml-opacity: 0.6;
opacity: 0.6;
}
/* 图片管理样式 */
#online {
width: 100%;
height: 336px;
padding: 10px 0 0 0;
}
#online #fileList{
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
position: relative;
}
#online ul {
display: block;
list-style: none;
margin: 0;
padding: 0;
}
#online li {
float: left;
display: block;
list-style: none;
padding: 0;
width: 113px;
height: 113px;
margin: 0 0 9px 9px;
*margin: 0 0 6px 6px;
background-color: #eee;
overflow: hidden;
cursor: pointer;
position: relative;
}
#online li.clearFloat {
float: none;
clear: both;
display: block;
width:0;
height:0;
margin: 0;
padding: 0;
}
#online li img {
cursor: pointer;
}
#online li div.file-wrapper {
cursor: pointer;
position: absolute;
display: block;
width: 111px;
height: 111px;
border: 1px solid #eee;
background: url("./images/bg.png") repeat;
}
#online li div span.file-title{
display: block;
padding: 0 3px;
margin: 3px 0 0 0;
font-size: 12px;
height: 13px;
color: #555555;
text-align: center;
width: 107px;
white-space: nowrap;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
}
#online li .icon {
cursor: pointer;
width: 113px;
height: 113px;
position: absolute;
top: 0;
left: 0;
z-index: 2;
border: 0;
background-repeat: no-repeat;
}
#online li .icon:hover {
width: 107px;
height: 107px;
border: 3px solid #1094fa;
}
#online li.selected .icon {
background-image: url(images/success.png);
background-image: url(images/success.gif) \9;
background-position: 75px 75px;
}
#online li.selected .icon:hover {
width: 107px;
height: 107px;
border: 3px solid #1094fa;
background-position: 72px 72px;
}
/* 在线文件的文件预览图标 */
i.file-preview {
display: block;
margin: 10px auto;
width: 70px;
height: 70px;
background-image: url("./images/file-icons.png");
background-image: url("./images/file-icons.gif") \9;
background-position: -140px center;
background-repeat: no-repeat;
}
i.file-preview.file-type-dir{
background-position: 0 center;
}
i.file-preview.file-type-file{
background-position: -140px center;
}
i.file-preview.file-type-filelist{
background-position: -210px center;
}
i.file-preview.file-type-zip,
i.file-preview.file-type-rar,
i.file-preview.file-type-7z,
i.file-preview.file-type-tar,
i.file-preview.file-type-gz,
i.file-preview.file-type-bz2{
background-position: -280px center;
}
i.file-preview.file-type-xls,
i.file-preview.file-type-xlsx{
background-position: -350px center;
}
i.file-preview.file-type-doc,
i.file-preview.file-type-docx{
background-position: -420px center;
}
i.file-preview.file-type-ppt,
i.file-preview.file-type-pptx{
background-position: -490px center;
}
i.file-preview.file-type-vsd{
background-position: -560px center;
}
i.file-preview.file-type-pdf{
background-position: -630px center;
}
i.file-preview.file-type-txt,
i.file-preview.file-type-md,
i.file-preview.file-type-json,
i.file-preview.file-type-htm,
i.file-preview.file-type-xml,
i.file-preview.file-type-html,
i.file-preview.file-type-js,
i.file-preview.file-type-css,
i.file-preview.file-type-php,
i.file-preview.file-type-jsp,
i.file-preview.file-type-asp{
background-position: -700px center;
}
i.file-preview.file-type-apk{
background-position: -770px center;
}
i.file-preview.file-type-exe{
background-position: -840px center;
}
i.file-preview.file-type-ipa{
background-position: -910px center;
}
i.file-preview.file-type-mp4,
i.file-preview.file-type-swf,
i.file-preview.file-type-mkv,
i.file-preview.file-type-avi,
i.file-preview.file-type-flv,
i.file-preview.file-type-mov,
i.file-preview.file-type-mpg,
i.file-preview.file-type-mpeg,
i.file-preview.file-type-ogv,
i.file-preview.file-type-webm,
i.file-preview.file-type-rm,
i.file-preview.file-type-rmvb{
background-position: -980px center;
}
i.file-preview.file-type-ogg,
i.file-preview.file-type-wav,
i.file-preview.file-type-wmv,
i.file-preview.file-type-mid,
i.file-preview.file-type-mp3{
background-position: -1050px center;
}
i.file-preview.file-type-jpg,
i.file-preview.file-type-jpeg,
i.file-preview.file-type-gif,
i.file-preview.file-type-bmp,
i.file-preview.file-type-png,
i.file-preview.file-type-psd{
background-position: -140px center;
}

View File

@ -0,0 +1,60 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>ueditor图片对话框</title>
<script type="text/javascript" src="../internal.js"></script>
<!-- jquery -->
<script type="text/javascript" src="../../third-party/jquery-1.10.2.min.js"></script>
<!-- webuploader -->
<script src="../../third-party/webuploader/webuploader.min.js"></script>
<link rel="stylesheet" type="text/css" href="../../third-party/webuploader/webuploader.css">
<!-- attachment dialog -->
<link rel="stylesheet" href="attachment.css" type="text/css" />
</head>
<body>
<div class="wrapper">
<div id="tabhead" class="tabhead">
<span class="tab focus" data-content-id="upload"><var id="lang_tab_upload"></var></span>
<span class="tab" data-content-id="online"><var id="lang_tab_online"></var></span>
</div>
<div id="tabbody" class="tabbody">
<!-- 上传图片 -->
<div id="upload" class="panel focus">
<div id="queueList" class="queueList">
<div class="statusBar element-invisible">
<div class="progress">
<span class="text">0%</span>
<span class="percentage"></span>
</div><div class="info"></div>
<div class="btns">
<div id="filePickerBtn"></div>
<div class="uploadBtn"><var id="lang_start_upload"></var></div>
</div>
</div>
<div id="dndArea" class="placeholder">
<div class="filePickerContainer">
<div id="filePickerReady"></div>
</div>
</div>
<ul class="filelist element-invisible">
<li id="filePickerBlock" class="filePickerBlock"></li>
</ul>
</div>
</div>
<!-- 在线图片 -->
<div id="online" class="panel">
<div id="fileList"><var id="lang_imgLoading"></var></div>
</div>
</div>
</div>
<script type="text/javascript" src="attachment.js"></script>
</body>
</html>

View File

@ -0,0 +1,760 @@
/**
* User: Jinqn
* Date: 14-04-08
* Time: 下午16:34
* 上传图片对话框逻辑代码,包括tab: 远程图片/上传图片/在线图片/搜索图片
*/
(function () {
var uploadFile,
onlineFile;
window.onload = function () {
initTabs();
initButtons();
};
/* 初始化tab标签 */
function initTabs() {
var tabs = $G('tabhead').children;
for (var i = 0; i < tabs.length; i++) {
domUtils.on(tabs[i], "click", function (e) {
var target = e.target || e.srcElement;
setTabFocus(target.getAttribute('data-content-id'));
});
}
setTabFocus('upload');
}
/* 初始化tabbody */
function setTabFocus(id) {
if(!id) return;
var i, bodyId, tabs = $G('tabhead').children;
for (i = 0; i < tabs.length; i++) {
bodyId = tabs[i].getAttribute('data-content-id')
if (bodyId == id) {
domUtils.addClass(tabs[i], 'focus');
domUtils.addClass($G(bodyId), 'focus');
} else {
domUtils.removeClasses(tabs[i], 'focus');
domUtils.removeClasses($G(bodyId), 'focus');
}
}
switch (id) {
case 'upload':
uploadFile = uploadFile || new UploadFile('queueList');
break;
case 'online':
onlineFile = onlineFile || new OnlineFile('fileList');
break;
}
}
/* 初始化onok事件 */
function initButtons() {
dialog.onok = function () {
var list = [], id, tabs = $G('tabhead').children;
for (var i = 0; i < tabs.length; i++) {
if (domUtils.hasClass(tabs[i], 'focus')) {
id = tabs[i].getAttribute('data-content-id');
break;
}
}
switch (id) {
case 'upload':
list = uploadFile.getInsertList();
var count = uploadFile.getQueueCount();
if (count) {
$('.info', '#queueList').html('<span style="color:red;">' + '还有2个未上传文件'.replace(/[\d]/, count) + '</span>');
return false;
}
break;
case 'online':
list = onlineFile.getInsertList();
break;
}
editor.execCommand('insertfile', list);
};
}
/* 上传附件 */
function UploadFile(target) {
this.$wrap = target.constructor == String ? $('#' + target) : $(target);
this.init();
}
UploadFile.prototype = {
init: function () {
this.fileList = [];
this.initContainer();
this.initUploader();
},
initContainer: function () {
this.$queue = this.$wrap.find('.filelist');
},
/* 初始化容器 */
initUploader: function () {
var _this = this,
$ = jQuery, // just in case. Make sure it's not an other libaray.
$wrap = _this.$wrap,
// 图片容器
$queue = $wrap.find('.filelist'),
// 状态栏包括进度和控制按钮
$statusBar = $wrap.find('.statusBar'),
// 文件总体选择信息
$info = $statusBar.find('.info'),
// 上传按钮
$upload = $wrap.find('.uploadBtn'),
// 上传按钮
$filePickerBtn = $wrap.find('.filePickerBtn'),
// 上传按钮
$filePickerBlock = $wrap.find('.filePickerBlock'),
// 没选择文件之前的内容
$placeHolder = $wrap.find('.placeholder'),
// 总体进度条
$progress = $statusBar.find('.progress').hide(),
// 添加的文件数量
fileCount = 0,
// 添加的文件总大小
fileSize = 0,
// 优化retina, 在retina下这个值是2
ratio = window.devicePixelRatio || 1,
// 缩略图大小
thumbnailWidth = 113 * ratio,
thumbnailHeight = 113 * ratio,
// 可能有pedding, ready, uploading, confirm, done.
state = '',
// 所有文件的进度信息key为file id
percentages = {},
supportTransition = (function () {
var s = document.createElement('p').style,
r = 'transition' in s ||
'WebkitTransition' in s ||
'MozTransition' in s ||
'msTransition' in s ||
'OTransition' in s;
s = null;
return r;
})(),
// WebUploader实例
uploader,
actionUrl = editor.getActionUrl(editor.getOpt('fileActionName')),
fileMaxSize = editor.getOpt('fileMaxSize'),
acceptExtensions = (editor.getOpt('fileAllowFiles') || []).join('').replace(/\./g, ',').replace(/^[,]/, '');;
if (!WebUploader.Uploader.support()) {
$('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();
return;
} else if (!editor.getOpt('fileActionName')) {
$('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();
return;
}
uploader = _this.uploader = WebUploader.create({
pick: {
id: '#filePickerReady',
label: lang.uploadSelectFile
},
swf: '../../third-party/webuploader/Uploader.swf',
server: actionUrl,
fileVal: editor.getOpt('fileFieldName'),
duplicate: true,
fileSingleSizeLimit: fileMaxSize,
compress: false
});
uploader.addButton({
id: '#filePickerBlock'
});
uploader.addButton({
id: '#filePickerBtn',
label: lang.uploadAddFile
});
setState('pedding');
// 当有文件添加进来时执行负责view的创建
function addFile(file) {
var $li = $('<li id="' + file.id + '">' +
'<p class="title">' + file.name + '</p>' +
'<p class="imgWrap"></p>' +
'<p class="progress"><span></span></p>' +
'</li>'),
$btns = $('<div class="file-panel">' +
'<span class="cancel">' + lang.uploadDelete + '</span>' +
'<span class="rotateRight">' + lang.uploadTurnRight + '</span>' +
'<span class="rotateLeft">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),
$prgress = $li.find('p.progress span'),
$wrap = $li.find('p.imgWrap'),
$info = $('<p class="error"></p>').hide().appendTo($li),
showError = function (code) {
switch (code) {
case 'exceed_size':
text = lang.errorExceedSize;
break;
case 'interrupt':
text = lang.errorInterrupt;
break;
case 'http':
text = lang.errorHttp;
break;
case 'not_allow_type':
text = lang.errorFileType;
break;
default:
text = lang.errorUploadRetry;
break;
}
$info.text(text).show();
};
if (file.getStatus() === 'invalid') {
showError(file.statusText);
} else {
$wrap.text(lang.uploadPreview);
if ('|png|jpg|jpeg|bmp|gif|'.indexOf('|'+file.ext.toLowerCase()+'|') == -1) {
$wrap.empty().addClass('notimage').append('<i class="file-preview file-type-' + file.ext.toLowerCase() + '"></i>' +
'<span class="file-title" title="' + file.name + '">' + file.name + '</span>');
} else {
if (browser.ie && browser.version <= 7) {
$wrap.text(lang.uploadNoPreview);
} else {
uploader.makeThumb(file, function (error, src) {
if (error || !src) {
$wrap.text(lang.uploadNoPreview);
} else {
var $img = $('<img src="' + src + '">');
$wrap.empty().append($img);
$img.on('error', function () {
$wrap.text(lang.uploadNoPreview);
});
}
}, thumbnailWidth, thumbnailHeight);
}
}
percentages[ file.id ] = [ file.size, 0 ];
file.rotation = 0;
/* 检查文件格式 */
if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {
showError('not_allow_type');
uploader.removeFile(file);
}
}
file.on('statuschange', function (cur, prev) {
if (prev === 'progress') {
$prgress.hide().width(0);
} else if (prev === 'queued') {
$li.off('mouseenter mouseleave');
$btns.remove();
}
// 成功
if (cur === 'error' || cur === 'invalid') {
showError(file.statusText);
percentages[ file.id ][ 1 ] = 1;
} else if (cur === 'interrupt') {
showError('interrupt');
} else if (cur === 'queued') {
percentages[ file.id ][ 1 ] = 0;
} else if (cur === 'progress') {
$info.hide();
$prgress.css('display', 'block');
} else if (cur === 'complete') {
}
$li.removeClass('state-' + prev).addClass('state-' + cur);
});
$li.on('mouseenter', function () {
$btns.stop().animate({height: 30});
});
$li.on('mouseleave', function () {
$btns.stop().animate({height: 0});
});
$btns.on('click', 'span', function () {
var index = $(this).index(),
deg;
switch (index) {
case 0:
uploader.removeFile(file);
return;
case 1:
file.rotation += 90;
break;
case 2:
file.rotation -= 90;
break;
}
if (supportTransition) {
deg = 'rotate(' + file.rotation + 'deg)';
$wrap.css({
'-webkit-transform': deg,
'-mos-transform': deg,
'-o-transform': deg,
'transform': deg
});
} else {
$wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');
}
});
$li.insertBefore($filePickerBlock);
}
// 负责view的销毁
function removeFile(file) {
var $li = $('#' + file.id);
delete percentages[ file.id ];
updateTotalProgress();
$li.off().find('.file-panel').off().end().remove();
}
function updateTotalProgress() {
var loaded = 0,
total = 0,
spans = $progress.children(),
percent;
$.each(percentages, function (k, v) {
total += v[ 0 ];
loaded += v[ 0 ] * v[ 1 ];
});
percent = total ? loaded / total : 0;
spans.eq(0).text(Math.round(percent * 100) + '%');
spans.eq(1).css('width', Math.round(percent * 100) + '%');
updateStatus();
}
function setState(val, files) {
if (val != state) {
var stats = uploader.getStats();
$upload.removeClass('state-' + state);
$upload.addClass('state-' + val);
switch (val) {
/* 未选择文件 */
case 'pedding':
$queue.addClass('element-invisible');
$statusBar.addClass('element-invisible');
$placeHolder.removeClass('element-invisible');
$progress.hide(); $info.hide();
uploader.refresh();
break;
/* 可以开始上传 */
case 'ready':
$placeHolder.addClass('element-invisible');
$queue.removeClass('element-invisible');
$statusBar.removeClass('element-invisible');
$progress.hide(); $info.show();
$upload.text(lang.uploadStart);
uploader.refresh();
break;
/* 上传中 */
case 'uploading':
$progress.show(); $info.hide();
$upload.text(lang.uploadPause);
break;
/* 暂停上传 */
case 'paused':
$progress.show(); $info.hide();
$upload.text(lang.uploadContinue);
break;
case 'confirm':
$progress.show(); $info.hide();
$upload.text(lang.uploadStart);
stats = uploader.getStats();
if (stats.successNum && !stats.uploadFailNum) {
setState('finish');
return;
}
break;
case 'finish':
$progress.hide(); $info.show();
if (stats.uploadFailNum) {
$upload.text(lang.uploadRetry);
} else {
$upload.text(lang.uploadStart);
}
break;
}
state = val;
updateStatus();
}
if (!_this.getQueueCount()) {
$upload.addClass('disabled')
} else {
$upload.removeClass('disabled')
}
}
function updateStatus() {
var text = '', stats;
if (state === 'ready') {
text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));
} else if (state === 'confirm') {
stats = uploader.getStats();
if (stats.uploadFailNum) {
text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);
}
} else {
stats = uploader.getStats();
text = lang.updateStatusFinish.replace('_', fileCount).
replace('_KB', WebUploader.formatSize(fileSize)).
replace('_', stats.successNum);
if (stats.uploadFailNum) {
text += lang.updateStatusError.replace('_', stats.uploadFailNum);
}
}
$info.html(text);
}
uploader.on('fileQueued', function (file) {
fileCount++;
fileSize += file.size;
if (fileCount === 1) {
$placeHolder.addClass('element-invisible');
$statusBar.show();
}
addFile(file);
});
uploader.on('fileDequeued', function (file) {
fileCount--;
fileSize -= file.size;
removeFile(file);
updateTotalProgress();
});
uploader.on('filesQueued', function (file) {
if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {
setState('ready');
}
updateTotalProgress();
});
uploader.on('all', function (type, files) {
switch (type) {
case 'uploadFinished':
setState('confirm', files);
break;
case 'startUpload':
/* 添加额外的GET参数 */
var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',
url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);
uploader.option('server', url);
setState('uploading', files);
break;
case 'stopUpload':
setState('paused', files);
break;
}
});
uploader.on('uploadBeforeSend', function (file, data, header) {
//这里可以通过data对象添加POST参数
header['X_Requested_With'] = 'XMLHttpRequest';
// HaoChuan9421
if(editor.options.headers && Object.prototype.toString.apply(editor.options.headers) === "[object Object]"){
for(var key in editor.options.headers){
header[key] = editor.options.headers[key]
}
}
});
uploader.on('uploadProgress', function (file, percentage) {
var $li = $('#' + file.id),
$percent = $li.find('.progress span');
$percent.css('width', percentage * 100 + '%');
percentages[ file.id ][ 1 ] = percentage;
updateTotalProgress();
});
uploader.on('uploadSuccess', function (file, ret) {
var $file = $('#' + file.id);
try {
var responseText = (ret._raw || ret),
json = utils.str2json(responseText);
if (json.state == 'SUCCESS') {
_this.fileList.push(json);
$file.append('<span class="success"></span>');
} else {
$file.find('.error').text(json.state).show();
}
} catch (e) {
$file.find('.error').text(lang.errorServerUpload).show();
}
});
uploader.on('uploadError', function (file, code) {
});
uploader.on('error', function (code, file) {
if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {
addFile(file);
}
});
uploader.on('uploadComplete', function (file, ret) {
});
$upload.on('click', function () {
if ($(this).hasClass('disabled')) {
return false;
}
if (state === 'ready') {
uploader.upload();
} else if (state === 'paused') {
uploader.upload();
} else if (state === 'uploading') {
uploader.stop();
}
});
$upload.addClass('state-' + state);
updateTotalProgress();
},
getQueueCount: function () {
var file, i, status, readyFile = 0, files = this.uploader.getFiles();
for (i = 0; file = files[i++]; ) {
status = file.getStatus();
if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;
}
return readyFile;
},
getInsertList: function () {
var i, link, data, list = [],
prefix = editor.getOpt('fileUrlPrefix');
for (i = 0; i < this.fileList.length; i++) {
data = this.fileList[i];
link = data.url;
list.push({
title: data.original || link.substr(link.lastIndexOf('/') + 1),
url: prefix + link
});
}
return list;
}
};
/* 在线附件 */
function OnlineFile(target) {
this.container = utils.isString(target) ? document.getElementById(target) : target;
this.init();
}
OnlineFile.prototype = {
init: function () {
this.initContainer();
this.initEvents();
this.initData();
},
/* 初始化容器 */
initContainer: function () {
this.container.innerHTML = '';
this.list = document.createElement('ul');
this.clearFloat = document.createElement('li');
domUtils.addClass(this.list, 'list');
domUtils.addClass(this.clearFloat, 'clearFloat');
this.list.appendChild(this.clearFloat);
this.container.appendChild(this.list);
},
/* 初始化滚动事件,滚动到地步自动拉取数据 */
initEvents: function () {
var _this = this;
/* 滚动拉取图片 */
domUtils.on($G('fileList'), 'scroll', function(e){
var panel = this;
if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {
_this.getFileData();
}
});
/* 选中图片 */
domUtils.on(this.list, 'click', function (e) {
var target = e.target || e.srcElement,
li = target.parentNode;
if (li.tagName.toLowerCase() == 'li') {
if (domUtils.hasClass(li, 'selected')) {
domUtils.removeClasses(li, 'selected');
} else {
domUtils.addClass(li, 'selected');
}
}
});
},
/* 初始化第一次的数据 */
initData: function () {
/* 拉取数据需要使用的值 */
this.state = 0;
this.listSize = editor.getOpt('fileManagerListSize');
this.listIndex = 0;
this.listEnd = false;
/* 第一次拉取数据 */
this.getFileData();
},
/* 向后台拉取图片列表数据 */
getFileData: function () {
var _this = this;
if(!_this.listEnd && !this.isLoadingData) {
this.isLoadingData = true;
ajax.request(editor.getActionUrl(editor.getOpt('fileManagerActionName')), {
timeout: 100000,
data: utils.extend({
start: this.listIndex,
size: this.listSize
}, editor.queryCommandValue('serverparam')),
method: 'get',
onsuccess: function (r) {
try {
var json = eval('(' + r.responseText + ')');
if (json.state == 'SUCCESS') {
_this.pushData(json.list);
_this.listIndex = parseInt(json.start) + parseInt(json.list.length);
if(_this.listIndex >= json.total) {
_this.listEnd = true;
}
_this.isLoadingData = false;
}
} catch (e) {
if(r.responseText.indexOf('ue_separate_ue') != -1) {
var list = r.responseText.split(r.responseText);
_this.pushData(list);
_this.listIndex = parseInt(list.length);
_this.listEnd = true;
_this.isLoadingData = false;
}
}
},
onerror: function () {
_this.isLoadingData = false;
}
});
}
},
/* 添加图片到列表界面上 */
pushData: function (list) {
var i, item, img, filetype, preview, icon, _this = this,
urlPrefix = editor.getOpt('fileManagerUrlPrefix');
for (i = 0; i < list.length; i++) {
if(list[i] && list[i].url) {
item = document.createElement('li');
icon = document.createElement('span');
filetype = list[i].url.substr(list[i].url.lastIndexOf('.') + 1);
if ( "png|jpg|jpeg|gif|bmp".indexOf(filetype) != -1 ) {
preview = document.createElement('img');
domUtils.on(preview, 'load', (function(image){
return function(){
_this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);
};
})(preview));
preview.width = 113;
preview.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );
} else {
var ic = document.createElement('i'),
textSpan = document.createElement('span');
textSpan.innerHTML = list[i].url.substr(list[i].url.lastIndexOf('/') + 1);
preview = document.createElement('div');
preview.appendChild(ic);
preview.appendChild(textSpan);
domUtils.addClass(preview, 'file-wrapper');
domUtils.addClass(textSpan, 'file-title');
domUtils.addClass(ic, 'file-type-' + filetype);
domUtils.addClass(ic, 'file-preview');
}
domUtils.addClass(icon, 'icon');
item.setAttribute('data-url', urlPrefix + list[i].url);
if (list[i].original) {
item.setAttribute('data-title', list[i].original);
}
item.appendChild(preview);
item.appendChild(icon);
this.list.insertBefore(item, this.clearFloat);
}
}
},
/* 改变图片大小 */
scale: function (img, w, h, type) {
var ow = img.width,
oh = img.height;
if (type == 'justify') {
if (ow >= oh) {
img.width = w;
img.height = h * oh / ow;
img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
} else {
img.width = w * ow / oh;
img.height = h;
img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
}
} else {
if (ow >= oh) {
img.width = w * ow / oh;
img.height = h;
img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
} else {
img.width = w;
img.height = h * oh / ow;
img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
}
}
},
getInsertList: function () {
var i, lis = this.list.children, list = [];
for (i = 0; i < lis.length; i++) {
if (domUtils.hasClass(lis[i], 'selected')) {
var url = lis[i].getAttribute('data-url');
var title = lis[i].getAttribute('data-title') || url.substr(url.lastIndexOf('/') + 1);
list.push({
title: title,
url: url
});
}
}
return list;
}
};
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1007 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,94 @@
.wrapper{ width: 424px;margin: 10px auto; zoom:1;position: relative}
.tabbody{height:225px;}
.tabbody .panel { position: absolute;width:100%; height:100%;background: #fff; display: none;}
.tabbody .focus { display: block;}
body{font-size: 12px;color: #888;overflow: hidden;}
input,label{vertical-align:middle}
.clear{clear: both;}
.pl{padding-left: 18px;padding-left: 23px\9;}
#imageList {width: 420px;height: 215px;margin-top: 10px;overflow: hidden;overflow-y: auto;}
#imageList div {float: left;width: 100px;height: 95px;margin: 5px 10px;}
#imageList img {cursor: pointer;border: 2px solid white;}
.bgarea{margin: 10px;padding: 5px;height: 84%;border: 1px solid #A8A297;}
.content div{margin: 10px 0 10px 5px;}
.content .iptradio{margin: 0px 5px 5px 0px;}
.txt{width:280px;}
.wrapcolor{height: 19px;}
div.color{float: left;margin: 0;}
#colorPicker{width: 17px;height: 17px;border: 1px solid #CCC;display: inline-block;border-radius: 3px;box-shadow: 2px 2px 5px #D3D6DA;margin: 0;float: left;}
div.alignment,#custom{margin-left: 23px;margin-left: 28px\9;}
#custom input{height: 15px;min-height: 15px;width:20px;}
#repeatType{width:100px;}
/* 图片管理样式 */
#imgManager {
width: 100%;
height: 225px;
}
#imgManager #imageList{
width: 100%;
overflow-x: hidden;
overflow-y: auto;
}
#imgManager ul {
display: block;
list-style: none;
margin: 0;
padding: 0;
}
#imgManager li {
float: left;
display: block;
list-style: none;
padding: 0;
width: 113px;
height: 113px;
margin: 9px 0 0 19px;
background-color: #eee;
overflow: hidden;
cursor: pointer;
position: relative;
}
#imgManager li.clearFloat {
float: none;
clear: both;
display: block;
width:0;
height:0;
margin: 0;
padding: 0;
}
#imgManager li img {
cursor: pointer;
}
#imgManager li .icon {
cursor: pointer;
width: 113px;
height: 113px;
position: absolute;
top: 0;
left: 0;
z-index: 2;
border: 0;
background-repeat: no-repeat;
}
#imgManager li .icon:hover {
width: 107px;
height: 107px;
border: 3px solid #1094fa;
}
#imgManager li.selected .icon {
background-image: url(images/success.png);
background-position: 75px 75px;
}
#imgManager li.selected .icon:hover {
width: 107px;
height: 107px;
border: 3px solid #1094fa;
background-position: 72px 72px;
}

View File

@ -0,0 +1,56 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<script type="text/javascript" src="../internal.js"></script>
<link rel="stylesheet" type="text/css" href="background.css">
</head>
<body>
<div id="bg_container" class="wrapper">
<div id="tabHeads" class="tabhead">
<span class="focus" data-content-id="normal"><var id="lang_background_normal"></var></span>
<span class="" data-content-id="imgManager"><var id="lang_background_local"></var></span>
</div>
<div id="tabBodys" class="tabbody">
<div id="normal" class="panel focus">
<fieldset class="bgarea">
<legend><var id="lang_background_set"></var></legend>
<div class="content">
<div>
<label><input id="nocolorRadio" class="iptradio" type="radio" name="t" value="none" checked="checked"><var id="lang_background_none"></var></label>
<label><input id="coloredRadio" class="iptradio" type="radio" name="t" value="color"><var id="lang_background_colored"></var></label>
</div>
<div class="wrapcolor pl">
<div class="color">
<var id="lang_background_color"></var>:
</div>
<div id="colorPicker"></div>
<div class="clear"></div>
</div>
<div class="wrapcolor pl">
<label><var id="lang_background_netimg"></var>:</label><input class="txt" type="text" id="url">
</div>
<div id="alignment" class="alignment">
<var id="lang_background_align"></var>:<select id="repeatType">
<option value="center"></option>
<option value="repeat-x"></option>
<option value="repeat-y"></option>
<option value="repeat"></option>
<option value="self"></option>
</select>
</div>
<div id="custom" >
<var id="lang_background_position"></var>:x:<input type="text" size="1" id="x" maxlength="4" value="0">px&nbsp;&nbsp;y:<input type="text" size="1" id="y" maxlength="4" value="0">px
</div>
</div>
</fieldset>
</div>
<div id="imgManager" class="panel">
<div id="imageList" style=""></div>
</div>
</div>
</div>
<script type="text/javascript" src="background.js"></script>
</body>
</html>

View File

@ -0,0 +1,376 @@
(function () {
var onlineImage,
backupStyle = editor.queryCommandValue('background');
window.onload = function () {
initTabs();
initColorSelector();
};
/* 初始化tab标签 */
function initTabs(){
var tabs = $G('tabHeads').children;
for (var i = 0; i < tabs.length; i++) {
domUtils.on(tabs[i], "click", function (e) {
var target = e.target || e.srcElement;
for (var j = 0; j < tabs.length; j++) {
if(tabs[j] == target){
tabs[j].className = "focus";
var contentId = tabs[j].getAttribute('data-content-id');
$G(contentId).style.display = "block";
if(contentId == 'imgManager') {
initImagePanel();
}
}else {
tabs[j].className = "";
$G(tabs[j].getAttribute('data-content-id')).style.display = "none";
}
}
});
}
}
/* 初始化颜色设置 */
function initColorSelector () {
var obj = editor.queryCommandValue('background');
if (obj) {
var color = obj['background-color'],
repeat = obj['background-repeat'] || 'repeat',
image = obj['background-image'] || '',
position = obj['background-position'] || 'center center',
pos = position.split(' '),
x = parseInt(pos[0]) || 0,
y = parseInt(pos[1]) || 0;
if(repeat == 'no-repeat' && (x || y)) repeat = 'self';
image = image.match(/url[\s]*\(([^\)]*)\)/);
image = image ? image[1]:'';
updateFormState('colored', color, image, repeat, x, y);
} else {
updateFormState();
}
var updateHandler = function () {
updateFormState();
updateBackground();
}
domUtils.on($G('nocolorRadio'), 'click', updateBackground);
domUtils.on($G('coloredRadio'), 'click', updateHandler);
domUtils.on($G('url'), 'keyup', function(){
if($G('url').value && $G('alignment').style.display == "none") {
utils.each($G('repeatType').children, function(item){
item.selected = ('repeat' == item.getAttribute('value') ? 'selected':false);
});
}
updateHandler();
});
domUtils.on($G('repeatType'), 'change', updateHandler);
domUtils.on($G('x'), 'keyup', updateBackground);
domUtils.on($G('y'), 'keyup', updateBackground);
initColorPicker();
}
/* 初始化颜色选择器 */
function initColorPicker() {
var me = editor,
cp = $G("colorPicker");
/* 生成颜色选择器ui对象 */
var popup = new UE.ui.Popup({
content: new UE.ui.ColorPicker({
noColorText: me.getLang("clearColor"),
editor: me,
onpickcolor: function (t, color) {
updateFormState('colored', color);
updateBackground();
UE.ui.Popup.postHide();
},
onpicknocolor: function (t, color) {
updateFormState('colored', 'transparent');
updateBackground();
UE.ui.Popup.postHide();
}
}),
editor: me,
onhide: function () {
}
});
/* 设置颜色选择器 */
domUtils.on(cp, "click", function () {
popup.showAnchor(this);
});
domUtils.on(document, 'mousedown', function (evt) {
var el = evt.target || evt.srcElement;
UE.ui.Popup.postHide(el);
});
domUtils.on(window, 'scroll', function () {
UE.ui.Popup.postHide();
});
}
/* 初始化在线图片列表 */
function initImagePanel() {
onlineImage = onlineImage || new OnlineImage('imageList');
}
/* 更新背景色设置面板 */
function updateFormState (radio, color, url, align, x, y) {
var nocolorRadio = $G('nocolorRadio'),
coloredRadio = $G('coloredRadio');
if(radio) {
nocolorRadio.checked = (radio == 'colored' ? false:'checked');
coloredRadio.checked = (radio == 'colored' ? 'checked':false);
}
if(color) {
domUtils.setStyle($G("colorPicker"), "background-color", color);
}
if(url && /^\//.test(url)) {
var a = document.createElement('a');
a.href = url;
browser.ie && (a.href = a.href);
url = browser.ie ? a.href:(a.protocol + '//' + a.host + a.pathname + a.search + a.hash);
}
if(url || url === '') {
$G('url').value = url;
}
if(align) {
utils.each($G('repeatType').children, function(item){
item.selected = (align == item.getAttribute('value') ? 'selected':false);
});
}
if(x || y) {
$G('x').value = parseInt(x) || 0;
$G('y').value = parseInt(y) || 0;
}
$G('alignment').style.display = coloredRadio.checked && $G('url').value ? '':'none';
$G('custom').style.display = coloredRadio.checked && $G('url').value && $G('repeatType').value == 'self' ? '':'none';
}
/* 更新背景颜色 */
function updateBackground () {
if ($G('coloredRadio').checked) {
var color = domUtils.getStyle($G("colorPicker"), "background-color"),
bgimg = $G("url").value,
align = $G("repeatType").value,
backgroundObj = {
"background-repeat": "no-repeat",
"background-position": "center center"
};
if (color) backgroundObj["background-color"] = color;
if (bgimg) backgroundObj["background-image"] = 'url(' + bgimg + ')';
if (align == 'self') {
backgroundObj["background-position"] = $G("x").value + "px " + $G("y").value + "px";
} else if (align == 'repeat-x' || align == 'repeat-y' || align == 'repeat') {
backgroundObj["background-repeat"] = align;
}
editor.execCommand('background', backgroundObj);
} else {
editor.execCommand('background', null);
}
}
/* 在线图片 */
function OnlineImage(target) {
this.container = utils.isString(target) ? document.getElementById(target) : target;
this.init();
}
OnlineImage.prototype = {
init: function () {
this.reset();
this.initEvents();
},
/* 初始化容器 */
initContainer: function () {
this.container.innerHTML = '';
this.list = document.createElement('ul');
this.clearFloat = document.createElement('li');
domUtils.addClass(this.list, 'list');
domUtils.addClass(this.clearFloat, 'clearFloat');
this.list.id = 'imageListUl';
this.list.appendChild(this.clearFloat);
this.container.appendChild(this.list);
},
/* 初始化滚动事件,滚动到地步自动拉取数据 */
initEvents: function () {
var _this = this;
/* 滚动拉取图片 */
domUtils.on($G('imageList'), 'scroll', function(e){
var panel = this;
if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {
_this.getImageData();
}
});
/* 选中图片 */
domUtils.on(this.container, 'click', function (e) {
var target = e.target || e.srcElement,
li = target.parentNode,
nodes = $G('imageListUl').childNodes;
if (li.tagName.toLowerCase() == 'li') {
updateFormState('nocolor', null, '');
for (var i = 0, node; node = nodes[i++];) {
if (node == li && !domUtils.hasClass(node, 'selected')) {
domUtils.addClass(node, 'selected');
updateFormState('colored', null, li.firstChild.getAttribute("_src"), 'repeat');
} else {
domUtils.removeClasses(node, 'selected');
}
}
updateBackground();
}
});
},
/* 初始化第一次的数据 */
initData: function () {
/* 拉取数据需要使用的值 */
this.state = 0;
this.listSize = editor.getOpt('imageManagerListSize');
this.listIndex = 0;
this.listEnd = false;
/* 第一次拉取数据 */
this.getImageData();
},
/* 重置界面 */
reset: function() {
this.initContainer();
this.initData();
},
/* 向后台拉取图片列表数据 */
getImageData: function () {
var _this = this;
if(!_this.listEnd && !this.isLoadingData) {
this.isLoadingData = true;
var url = editor.getActionUrl(editor.getOpt('imageManagerActionName')),
isJsonp = utils.isCrossDomainUrl(url);
ajax.request(url, {
'timeout': 100000,
'dataType': isJsonp ? 'jsonp':'',
'data': utils.extend({
start: this.listIndex,
size: this.listSize
}, editor.queryCommandValue('serverparam')),
'method': 'get',
'onsuccess': function (r) {
try {
var json = isJsonp ? r:eval('(' + r.responseText + ')');
if (json.state == 'SUCCESS') {
_this.pushData(json.list);
_this.listIndex = parseInt(json.start) + parseInt(json.list.length);
if(_this.listIndex >= json.total) {
_this.listEnd = true;
}
_this.isLoadingData = false;
}
} catch (e) {
if(r.responseText.indexOf('ue_separate_ue') != -1) {
var list = r.responseText.split(r.responseText);
_this.pushData(list);
_this.listIndex = parseInt(list.length);
_this.listEnd = true;
_this.isLoadingData = false;
}
}
},
'onerror': function () {
_this.isLoadingData = false;
}
});
}
},
/* 添加图片到列表界面上 */
pushData: function (list) {
var i, item, img, icon, _this = this,
urlPrefix = editor.getOpt('imageManagerUrlPrefix');
for (i = 0; i < list.length; i++) {
if(list[i] && list[i].url) {
item = document.createElement('li');
img = document.createElement('img');
icon = document.createElement('span');
domUtils.on(img, 'load', (function(image){
return function(){
_this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);
}
})(img));
img.width = 113;
img.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );
img.setAttribute('_src', urlPrefix + list[i].url);
domUtils.addClass(icon, 'icon');
item.appendChild(img);
item.appendChild(icon);
this.list.insertBefore(item, this.clearFloat);
}
}
},
/* 改变图片大小 */
scale: function (img, w, h, type) {
var ow = img.width,
oh = img.height;
if (type == 'justify') {
if (ow >= oh) {
img.width = w;
img.height = h * oh / ow;
img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
} else {
img.width = w * ow / oh;
img.height = h;
img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
}
} else {
if (ow >= oh) {
img.width = w * ow / oh;
img.height = h;
img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';
} else {
img.width = w;
img.height = h * oh / ow;
img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';
}
}
},
getInsertList: function () {
var i, lis = this.list.children, list = [], align = getAlign();
for (i = 0; i < lis.length; i++) {
if (domUtils.hasClass(lis[i], 'selected')) {
var img = lis[i].firstChild,
src = img.getAttribute('_src');
list.push({
src: src,
_src: src,
floatStyle: align
});
}
}
return list;
}
};
dialog.onok = function () {
updateBackground();
editor.fireEvent('saveScene');
};
dialog.oncancel = function () {
editor.execCommand('background', backupStyle);
};
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,65 @@
/*
* 图表配置文件
* */
//不同类型的配置
var typeConfig = [
{
chart: {
type: 'line'
},
plotOptions: {
line: {
dataLabels: {
enabled: false
},
enableMouseTracking: true
}
}
}, {
chart: {
type: 'line'
},
plotOptions: {
line: {
dataLabels: {
enabled: true
},
enableMouseTracking: false
}
}
}, {
chart: {
type: 'area'
}
}, {
chart: {
type: 'bar'
}
}, {
chart: {
type: 'column'
}
}, {
chart: {
plotBackgroundColor: null,
plotBorderWidth: null,
plotShadow: false
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: true,
color: '#000000',
connectorColor: '#000000',
formatter: function() {
return '<b>'+ this.point.name +'</b>: '+ ( Math.round( this.point.percentage*100 ) / 100 ) +' %';
}
}
}
}
}
];

View File

@ -0,0 +1,165 @@
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow-x: hidden;
}
.main {
width: 100%;
overflow: hidden;
}
.table-view {
height: 100%;
float: left;
margin: 20px;
width: 40%;
}
.table-view .table-container {
width: 100%;
margin-bottom: 50px;
overflow: scroll;
}
.table-view th {
padding: 5px 10px;
background-color: #F7F7F7;
}
.table-view td {
width: 50px;
text-align: center;
padding:0;
}
.table-container input {
width: 40px;
padding: 5px;
border: none;
outline: none;
}
.table-view caption {
font-size: 18px;
text-align: left;
}
.charts-view {
/*margin-left: 49%!important;*/
width: 50%;
margin-left: 49%;
height: 400px;
}
.charts-container {
border-left: 1px solid #c3c3c3;
}
.charts-format fieldset {
padding-left: 20px;
margin-bottom: 50px;
}
.charts-format legend {
padding-left: 10px;
padding-right: 10px;
}
.format-item-container {
padding: 20px;
}
.format-item-container label {
display: block;
margin: 10px 0;
}
.charts-format .data-item {
border: 1px solid black;
outline: none;
padding: 2px 3px;
}
/* 图表类型 */
.charts-type {
margin-top: 50px;
height: 300px;
}
.scroll-view {
border: 1px solid #c3c3c3;
border-left: none;
border-right: none;
overflow: hidden;
}
.scroll-container {
margin: 20px;
width: 100%;
overflow: hidden;
}
.scroll-bed {
width: 10000px;
_margin-top: 20px;
-webkit-transition: margin-left .5s ease;
-moz-transition: margin-left .5s ease;
transition: margin-left .5s ease;
}
.view-box {
display: inline-block;
*display: inline;
*zoom: 1;
margin-right: 20px;
border: 2px solid white;
line-height: 0;
overflow: hidden;
cursor: pointer;
}
.view-box img {
border: 1px solid #cecece;
}
.view-box.selected {
border-color: #7274A7;
}
.button-container {
margin-bottom: 20px;
text-align: center;
}
.button-container a {
display: inline-block;
width: 100px;
height: 25px;
line-height: 25px;
border: 1px solid #c2ccd1;
margin-right: 30px;
text-decoration: none;
color: black;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
}
.button-container a:HOVER {
background: #fcfcfc;
}
.button-container a:ACTIVE {
border-top-color: #c2ccd1;
box-shadow:inset 0 5px 4px -4px rgba(49, 49, 64, 0.1);
}
.edui-charts-not-data {
height: 100px;
line-height: 100px;
text-align: center;
}

View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<head>
<title>chart</title>
<meta chartset="utf-8">
<link rel="stylesheet" type="text/css" href="charts.css">
<script type="text/javascript" src="../internal.js"></script>
</head>
<body>
<div class="main">
<div class="table-view">
<h3><var id="lang_data_source"></var></h3>
<div id="tableContainer" class="table-container"></div>
<h3><var id="lang_chart_format"></var></h3>
<form name="data-form">
<div class="charts-format">
<fieldset>
<legend><var id="lang_data_align"></var></legend>
<div class="format-item-container">
<label>
<input type="radio" class="format-ctrl not-pie-item" name="charts-format" value="1" checked="checked">
<var id="lang_chart_align_same"></var>
</label>
<label>
<input type="radio" class="format-ctrl not-pie-item" name="charts-format" value="-1">
<var id="lang_chart_align_reverse"></var>
</label>
<br>
</div>
</fieldset>
<fieldset>
<legend><var id="lang_chart_title"></var></legend>
<div class="format-item-container">
<label>
<var id="lang_chart_main_title"></var><input type="text" name="title" class="data-item">
</label>
<label>
<var id="lang_chart_sub_title"></var><input type="text" name="sub-title" class="data-item not-pie-item">
</label>
<label>
<var id="lang_chart_x_title"></var><input type="text" name="x-title" class="data-item not-pie-item">
</label>
<label>
<var id="lang_chart_y_title"></var><input type="text" name="y-title" class="data-item not-pie-item">
</label>
</div>
</fieldset>
<fieldset>
<legend><var id="lang_chart_tip"></var></legend>
<div class="format-item-container">
<label>
<var id="lang_cahrt_tip_prefix"></var>
<input type="text" id="tipInput" name="tip" class="data-item" disabled="disabled">
</label>
<p><var id="lang_cahrt_tip_description"></var></p>
</div>
</fieldset>
<fieldset>
<legend><var id="lang_chart_data_unit"></var></legend>
<div class="format-item-container">
<label><var id="lang_chart_data_unit_title"></var><input type="text" name="unit" class="data-item"></label>
<p><var id="lang_chart_data_unit_description"></var></p>
</div>
</fieldset>
</div>
</form>
</div>
<div class="charts-view">
<div id="chartsContainer" class="charts-container"></div>
<div id="chartsType" class="charts-type">
<h3><var id="lang_chart_type"></var></h3>
<div class="scroll-view">
<div class="scroll-container">
<div id="scrollBed" class="scroll-bed"></div>
</div>
<div id="buttonContainer" class="button-container">
<a href="#" data-title="prev"><var id="lang_prev_btn"></var></a>
<a href="#" data-title="next"><var id="lang_next_btn"></var></a>
</div>
</div>
</div>
</div>
</div>
<script src="../../third-party/jquery-1.10.2.min.js"></script>
<script src="../../third-party/highcharts/highcharts.js"></script>
<script src="chart.config.js"></script>
<script src="charts.js"></script>
</body>
</html>

View File

@ -0,0 +1,519 @@
/*
* 图片转换对话框脚本
**/
var tableData = [],
//编辑器页面table
editorTable = null,
chartsConfig = window.typeConfig,
resizeTimer = null,
//初始默认图表类型
currentChartType = 0;
window.onload = function () {
editorTable = domUtils.findParentByTagName( editor.selection.getRange().startContainer, 'table', true);
//未找到表格 显示错误页面
if ( !editorTable ) {
document.body.innerHTML = "<div class='edui-charts-not-data'>未找到数据</div>";
return;
}
//初始化图表类型选择
initChartsTypeView();
renderTable( editorTable );
initEvent();
initUserConfig( editorTable.getAttribute( "data-chart" ) );
$( "#scrollBed .view-box:eq("+ currentChartType +")" ).trigger( "click" );
updateViewType( currentChartType );
dialog.addListener( "resize", function () {
if ( resizeTimer != null ) {
window.clearTimeout( resizeTimer );
}
resizeTimer = window.setTimeout( function () {
resizeTimer = null;
renderCharts();
}, 500 );
} );
};
function initChartsTypeView () {
var contents = [];
for ( var i = 0, len = chartsConfig.length; i<len; i++ ) {
contents.push( '<div class="view-box" data-chart-type="'+ i +'"><img width="300" src="images/charts'+ i +'.png"></div>' );
}
$( "#scrollBed" ).html( contents.join( "" ) );
}
//渲染table 以便用户修改数据
function renderTable ( table ) {
var tableHtml = [];
//构造数据
for ( var i = 0, row; row = table.rows[ i ]; i++ ) {
tableData[ i ] = [];
tableHtml[ i ] = [];
for ( var j = 0, cell; cell = row.cells[ j ]; j++ ) {
var value = getCellValue( cell );
if ( i > 0 && j > 0 ) {
value = +value;
}
if ( i === 0 || j === 0 ) {
tableHtml[ i ].push( '<th>'+ value +'</th>' );
} else {
tableHtml[ i ].push( '<td><input type="text" class="data-item" value="'+ value +'"></td>' );
}
tableData[ i ][ j ] = value;
}
tableHtml[ i ] = tableHtml[ i ].join( "" );
}
//draw 表格
$( "#tableContainer" ).html( '<table id="showTable" border="1"><tbody><tr>'+ tableHtml.join( "</tr><tr>" ) +'</tr></tbody></table>' );
}
/*
* 根据表格已有的图表属性初始化当前图表属性
*/
function initUserConfig ( config ) {
var parsedConfig = {};
if ( !config ) {
return;
}
config = config.split( ";" );
$.each( config, function ( index, item ) {
item = item.split( ":" );
parsedConfig[ item[ 0 ] ] = item[ 1 ];
} );
setUserConfig( parsedConfig );
}
function initEvent () {
var cacheValue = null,
//图表类型数
typeViewCount = chartsConfig.length- 1,
$chartsTypeViewBox = $( '#scrollBed .view-box' );
$( ".charts-format" ).delegate( ".format-ctrl", "change", function () {
renderCharts();
} )
$( ".table-view" ).delegate( ".data-item", "focus", function () {
cacheValue = this.value;
} ).delegate( ".data-item", "blur", function () {
if ( this.value !== cacheValue ) {
renderCharts();
}
cacheValue = null;
} );
$( "#buttonContainer" ).delegate( "a", "click", function (e) {
e.preventDefault();
if ( this.getAttribute( "data-title" ) === 'prev' ) {
if ( currentChartType > 0 ) {
currentChartType--;
updateViewType( currentChartType );
}
} else {
if ( currentChartType < typeViewCount ) {
currentChartType++;
updateViewType( currentChartType );
}
}
} );
//图表类型变化
$( '#scrollBed' ).delegate( ".view-box", "click", function (e) {
var index = $( this ).attr( "data-chart-type" );
$chartsTypeViewBox.removeClass( "selected" );
$( $chartsTypeViewBox[ index ] ).addClass( "selected" );
currentChartType = index | 0;
//饼图 禁用部分配置
if ( currentChartType === chartsConfig.length - 1 ) {
disableNotPieConfig();
//启用完整配置
} else {
enableNotPieConfig();
}
renderCharts();
} );
}
function renderCharts () {
var data = collectData();
$('#chartsContainer').highcharts( $.extend( {}, chartsConfig[ currentChartType ], {
credits: {
enabled: false
},
exporting: {
enabled: false
},
title: {
text: data.title,
x: -20 //center
},
subtitle: {
text: data.subTitle,
x: -20
},
xAxis: {
title: {
text: data.xTitle
},
categories: data.categories
},
yAxis: {
title: {
text: data.yTitle
},
plotLines: [{
value: 0,
width: 1,
color: '#808080'
}]
},
tooltip: {
enabled: true,
valueSuffix: data.suffix
},
legend: {
layout: 'vertical',
align: 'right',
verticalAlign: 'middle',
borderWidth: 1
},
series: data.series
} ));
}
function updateViewType ( index ) {
$( "#scrollBed" ).css( 'marginLeft', -index*324+'px' );
}
function collectData () {
var form = document.forms[ 'data-form' ],
data = null;
if ( currentChartType !== chartsConfig.length - 1 ) {
data = getSeriesAndCategories();
$.extend( data, getUserConfig() );
//饼图数据格式
} else {
data = getSeriesForPieChart();
data.title = form[ 'title' ].value;
data.suffix = form[ 'unit' ].value;
}
return data;
}
/**
* 获取用户配置信息
*/
function getUserConfig () {
var form = document.forms[ 'data-form' ],
info = {
title: form[ 'title' ].value,
subTitle: form[ 'sub-title' ].value,
xTitle: form[ 'x-title' ].value,
yTitle: form[ 'y-title' ].value,
suffix: form[ 'unit' ].value,
//数据对齐方式
tableDataFormat: getTableDataFormat (),
//饼图提示文字
tip: $( "#tipInput" ).val()
};
return info;
}
function setUserConfig ( config ) {
var form = document.forms[ 'data-form' ];
config.title && ( form[ 'title' ].value = config.title );
config.subTitle && ( form[ 'sub-title' ].value = config.subTitle );
config.xTitle && ( form[ 'x-title' ].value = config.xTitle );
config.yTitle && ( form[ 'y-title' ].value = config.yTitle );
config.suffix && ( form[ 'unit' ].value = config.suffix );
config.dataFormat == "-1" && ( form[ 'charts-format' ][ 1 ].checked = true );
config.tip && ( form[ 'tip' ].value = config.tip );
currentChartType = config.chartType || 0;
}
function getSeriesAndCategories () {
var form = document.forms[ 'data-form' ],
series = [],
categories = [],
tmp = [],
tableData = getTableData();
//反转数据
if ( getTableDataFormat() === "-1" ) {
for ( var i = 0, len = tableData.length; i < len; i++ ) {
for ( var j = 0, jlen = tableData[ i ].length; j < jlen; j++ ) {
if ( !tmp[ j ] ) {
tmp[ j ] = [];
}
tmp[ j ][ i ] = tableData[ i ][ j ];
}
}
tableData = tmp;
}
categories = tableData[0].slice( 1 );
for ( var i = 1, data; data = tableData[ i ]; i++ ) {
series.push( {
name: data[ 0 ],
data: data.slice( 1 )
} );
}
return {
series: series,
categories: categories
};
}
/*
* 获取数据源数据对齐方式
*/
function getTableDataFormat () {
var form = document.forms[ 'data-form' ],
items = form['charts-format'];
return items[ 0 ].checked ? items[ 0 ].value : items[ 1 ].value;
}
/*
* 禁用非饼图类型的配置项
*/
function disableNotPieConfig() {
updateConfigItem( 'disable' );
}
/*
* 启用非饼图类型的配置项
*/
function enableNotPieConfig() {
updateConfigItem( 'enable' );
}
function updateConfigItem ( value ) {
var table = $( "#showTable" )[ 0 ],
isDisable = value === 'disable' ? true : false;
//table中的input处理
for ( var i = 2 , row; row = table.rows[ i ]; i++ ) {
for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {
$( "input", cell ).attr( "disabled", isDisable );
}
}
//其他项处理
$( "input.not-pie-item" ).attr( "disabled", isDisable );
$( "#tipInput" ).attr( "disabled", !isDisable )
}
/*
* 获取饼图数据
* 饼图的数据只取第一行的
**/
function getSeriesForPieChart () {
var series = {
type: 'pie',
name: $("#tipInput").val(),
data: []
},
tableData = getTableData();
for ( var j = 1, jlen = tableData[ 0 ].length; j < jlen; j++ ) {
var title = tableData[ 0 ][ j ],
val = tableData[ 1 ][ j ];
series.data.push( [ title, val ] );
}
return {
series: [ series ]
};
}
function getTableData () {
var table = document.getElementById( "showTable" ),
xCount = table.rows[0].cells.length - 1,
values = getTableInputValue();
for ( var i = 0, value; value = values[ i ]; i++ ) {
tableData[ Math.floor( i / xCount ) + 1 ][ i % xCount + 1 ] = values[ i ];
}
return tableData;
}
function getTableInputValue () {
var table = document.getElementById( "showTable" ),
inputs = table.getElementsByTagName( "input" ),
values = [];
for ( var i = 0, input; input = inputs[ i ]; i++ ) {
values.push( input.value | 0 );
}
return values;
}
function getCellValue ( cell ) {
var value = utils.trim( ( cell.innerText || cell.textContent || '' ) );
return value.replace( new RegExp( UE.dom.domUtils.fillChar, 'g' ), '' ).replace( /^\s+|\s+$/g, '' );
}
//dialog确认事件
dialog.onok = function () {
//收集信息
var form = document.forms[ 'data-form' ],
info = getUserConfig();
//添加图表类型
info.chartType = currentChartType;
//同步表格数据到编辑器
syncTableData();
//执行图表命令
editor.execCommand( 'charts', info );
};
/*
* 同步图表编辑视图的表格数据到编辑器里的原始表格
*/
function syncTableData () {
var tableData = getTableData();
for ( var i = 1, row; row = editorTable.rows[ i ]; i++ ) {
for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {
cell.innerHTML = tableData[ i ] [ j ];
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,43 @@
.jd img{
background:transparent url(images/jxface2.gif?v=1.1) no-repeat scroll left top;
cursor:pointer;width:35px;height:35px;display:block;
}
.pp img{
background:transparent url(images/fface.gif?v=1.1) no-repeat scroll left top;
cursor:pointer;width:25px;height:25px;display:block;
}
.ldw img{
background:transparent url(images/wface.gif?v=1.1) no-repeat scroll left top;
cursor:pointer;width:35px;height:35px;display:block;
}
.tsj img{
background:transparent url(images/tface.gif?v=1.1) no-repeat scroll left top;
cursor:pointer;width:35px;height:35px;display:block;
}
.cat img{
background:transparent url(images/cface.gif?v=1.1) no-repeat scroll left top;
cursor:pointer;width:35px;height:35px;display:block;
}
.bb img{
background:transparent url(images/bface.gif?v=1.1) no-repeat scroll left top;
cursor:pointer;width:35px;height:35px;display:block;
}
.youa img{
background:transparent url(images/yface.gif?v=1.1) no-repeat scroll left top;
cursor:pointer;width:35px;height:35px;display:block;
}
.smileytable td {height: 37px;}
#tabPanel{margin-left:5px;overflow: hidden;}
#tabContent {float:left;background:#FFFFFF;}
#tabContent div{display: none;width:480px;overflow:hidden;}
#tabIconReview.show{left:17px;display:block;}
.menuFocus{background:#ACCD3C;}
.menuDefault{background:#FFFFFF;}
#tabIconReview{position:absolute;left:406px;left:398px \9;top:41px;z-index:65533;width:90px;height:76px;}
img.review{width:90px;height:76px;border:2px solid #9cb945;background:#FFFFFF;background-position:center;background-repeat:no-repeat;}
.wrapper .tabbody{position:relative;float:left;clear:both;padding:10px;width: 95%;}
.tabbody table{width: 100%;}
.tabbody td{border:1px solid #BAC498;}
.tabbody td span{display: block;zoom:1;padding:0 4px;}

View File

@ -0,0 +1,54 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="robots" content="noindex, nofollow"/>
<script type="text/javascript" src="../internal.js"></script>
<link rel="stylesheet" type="text/css" href="emotion.css">
</head>
<body>
<div id="tabPanel" class="wrapper">
<div id="tabHeads" class="tabhead">
<span><var id="lang_input_choice"></var></span>
<span><var id="lang_input_Tuzki"></var></span>
<span><var id="lang_input_lvdouwa"></var></span>
<span><var id="lang_input_BOBO"></var></span>
<span><var id="lang_input_babyCat"></var></span>
<span><var id="lang_input_bubble"></var></span>
<span><var id="lang_input_youa"></var></span>
</div>
<div id="tabBodys" class="tabbody">
<div id="tab0"></div>
<div id="tab1"></div>
<div id="tab2"></div>
<div id="tab3"></div>
<div id="tab4"></div>
<div id="tab5"></div>
<div id="tab6"></div>
</div>
</div>
<div id="tabIconReview">
<img id='faceReview' class='review' src="../../themes/default/images/spacer.gif"/>
</div>
<script type="text/javascript" src="emotion.js"></script>
<script type="text/javascript">
var emotion = {
tabNum:7, //切换面板数量
SmilmgName:{ tab0:['j_00', 84], tab1:['t_00', 40], tab2:['w_00', 52], tab3:['B_00', 63], tab4:['C_00', 20], tab5:['i_f', 50], tab6:['y_00', 40] }, //图片前缀名
imageFolders:{ tab0:'jx2/', tab1:'tsj/', tab2:'ldw/', tab3:'bobo/', tab4:'babycat/', tab5:'face/', tab6:'youa/'}, //图片对应文件夹路径
imageCss:{tab0:'jd', tab1:'tsj', tab2:'ldw', tab3:'bb', tab4:'cat', tab5:'pp', tab6:'youa'}, //图片css类名
imageCssOffset:{tab0:35, tab1:35, tab2:35, tab3:35, tab4:35, tab5:25, tab6:35}, //图片偏移
SmileyInfor:{
tab0:['Kiss', 'Love', 'Yeah', '啊!', '背扭', '', '抖胸', '88', '', '瞌睡', '鲁拉', '拍砖', '揉脸', '生日快乐', '大笑', '瀑布汗~', '惊讶', '臭美', '傻笑', '抛媚眼', '发怒', '打酱油', '俯卧撑', '气愤', '?', '', '', '胜利', 'HI', 'KISS', '不说', '不要', '扯花', '大心', '', '大惊', '飞吻', '鬼脸', '害羞', '口水', '狂哭', '', '发财了', '吃西瓜', '套牢', '害羞', '庆祝', '我来了', '敲打', '晕了', '胜利', '臭美', '被打了', '贪吃', '迎接', '', '微笑', '亲吻', '调皮', '惊恐', '耍酷', '发火', '害羞', '汗水', '大哭', '', '加油', '', '你NB', '晕倒', '开心', '偷笑', '大哭', '滴汗', '叹气', '超赞', '??', '飞吻', '天使', '撒花', '生气', '被砸', '吓傻', '随意吐'],
tab1:['Kiss', 'Love', 'Yeah', '啊!', '背扭', '', '抖胸', '88', '', '瞌睡', '鲁拉', '拍砖', '揉脸', '生日快乐', '摊手', '睡觉', '瘫坐', '无聊', '星星闪', '旋转', '也不行', '郁闷', '正Music', '抓墙', '撞墙至死', '歪头', '戳眼', '飘过', '互相拍砖', '砍死你', '扔桌子', '少林寺', '什么?', '转头', '我爱牛奶', '我踢', '摇晃', '晕厥', '在笼子里', '震荡'],
tab2:['大笑', '瀑布汗~', '惊讶', '臭美', '傻笑', '抛媚眼', '发怒', '我错了', 'money', '气愤', '挑逗', '', '', '胜利', '委屈', '受伤', '说啥呢?', '闭嘴', '', '逗你玩儿', '飞吻', '眩晕', '魔法', '我来了', '睡了', '我打', '闭嘴', '', '打晕了', '刷牙', '爆揍', '炸弹', '倒立', '刮胡子', '邪恶的笑', '不要不要', '爱恋中', '放大仔细看', '偷窥', '超高兴', '', '松口气', '我跑', '享受', '修养', '', '', '啊~', '热烈欢迎', '打酱油', '俯卧撑', '?'],
tab3:['HI', 'KISS', '不说', '不要', '扯花', '大心', '', '大惊', '飞吻', '鬼脸', '害羞', '口水', '狂哭', '', '泪眼', '流泪', '生气', '吐舌', '喜欢', '旋转', '再见', '抓狂', '', '鄙视', '', '吐血', '', '打人', '蹦跳', '变脸', '扯肉', '吃To', '吃花', '吹泡泡糖', '大变身', '飞天舞', '回眸', '可怜', '猛抽', '泡泡', '苹果', '', '', '骚舞', '烧香', '', '套娃娃', '捅捅', '舞倒', '西红柿', '爱慕', '', '摇摆', '杂耍', '招财', '被殴', '被球闷', '大惊', '理想', '欧打', '呕吐', '', '吐痰'],
tab4:['发财了', '吃西瓜', '套牢', '害羞', '庆祝', '我来了', '敲打', '晕了', '胜利', '臭美', '被打了', '贪吃', '迎接', '', '', '幸运', '爱心', '', '送花', '选择'],
tab5:['微笑', '亲吻', '调皮', '惊讶', '耍酷', '发火', '害羞', '汗水', '大哭', '得意', '鄙视', '', '夸奖', '晕倒', '疑问', '媒婆', '狂吐', '青蛙', '发愁', '亲吻', '', '爱心', '心碎', '玫瑰', '礼物', '', '奸笑', '可爱', '得意', '呲牙', '暴汗', '楚楚可怜', '', '', '生气', '惊讶', '口水', '彩虹', '夜空', '太阳', '钱钱', '灯泡', '咖啡', '蛋糕', '音乐', '', '胜利', '', '鄙视', 'OK'],
tab6:['男兜', '女兜', '开心', '乖乖', '偷笑', '大笑', '抽泣', '大哭', '无奈', '滴汗', '叹气', '狂晕', '委屈', '超赞', '??', '疑问', '飞吻', '天使', '撒花', '生气', '被砸', '口水', '泪奔', '吓傻', '吐舌头', '点头', '随意吐', '旋转', '困困', '鄙视', '狂顶', '篮球', '再见', '欢迎光临', '恭喜发财', '稍等', '我在线', '恕不议价', '库房有货', '货在路上']
}
};
</script>
</body>
</html>

View File

@ -0,0 +1,186 @@
window.onload = function () {
editor.setOpt({
emotionLocalization:false
});
emotion.SmileyPath = editor.options.emotionLocalization === true ? 'images/' : "http://img.baidu.com/hi/";
emotion.SmileyBox = createTabList( emotion.tabNum );
emotion.tabExist = createArr( emotion.tabNum );
initImgName();
initEvtHandler( "tabHeads" );
};
function initImgName() {
for ( var pro in emotion.SmilmgName ) {
var tempName = emotion.SmilmgName[pro],
tempBox = emotion.SmileyBox[pro],
tempStr = "";
if ( tempBox.length ) return;
for ( var i = 1; i <= tempName[1]; i++ ) {
tempStr = tempName[0];
if ( i < 10 ) tempStr = tempStr + '0';
tempStr = tempStr + i + '.gif';
tempBox.push( tempStr );
}
}
}
function initEvtHandler( conId ) {
var tabHeads = $G( conId );
for ( var i = 0, j = 0; i < tabHeads.childNodes.length; i++ ) {
var tabObj = tabHeads.childNodes[i];
if ( tabObj.nodeType == 1 ) {
domUtils.on( tabObj, "click", (function ( index ) {
return function () {
switchTab( index );
};
})( j ) );
j++;
}
}
switchTab( 0 );
$G( "tabIconReview" ).style.display = 'none';
}
function InsertSmiley( url, evt ) {
var obj = {
src:editor.options.emotionLocalization ? editor.options.UEDITOR_HOME_URL + "dialogs/emotion/" + url : url
};
obj._src = obj.src;
editor.execCommand( 'insertimage', obj );
if ( !evt.ctrlKey ) {
dialog.popup.hide();
}
}
function switchTab( index ) {
autoHeight( index );
if ( emotion.tabExist[index] == 0 ) {
emotion.tabExist[index] = 1;
createTab( 'tab' + index );
}
//获取呈现元素句柄数组
var tabHeads = $G( "tabHeads" ).getElementsByTagName( "span" ),
tabBodys = $G( "tabBodys" ).getElementsByTagName( "div" ),
i = 0, L = tabHeads.length;
//隐藏所有呈现元素
for ( ; i < L; i++ ) {
tabHeads[i].className = "";
tabBodys[i].style.display = "none";
}
//显示对应呈现元素
tabHeads[index].className = "focus";
tabBodys[index].style.display = "block";
}
function autoHeight( index ) {
var iframe = dialog.getDom( "iframe" ),
parent = iframe.parentNode.parentNode;
switch ( index ) {
case 0:
iframe.style.height = "380px";
parent.style.height = "392px";
break;
case 1:
iframe.style.height = "220px";
parent.style.height = "232px";
break;
case 2:
iframe.style.height = "260px";
parent.style.height = "272px";
break;
case 3:
iframe.style.height = "300px";
parent.style.height = "312px";
break;
case 4:
iframe.style.height = "140px";
parent.style.height = "152px";
break;
case 5:
iframe.style.height = "260px";
parent.style.height = "272px";
break;
case 6:
iframe.style.height = "230px";
parent.style.height = "242px";
break;
default:
}
}
function createTab( tabName ) {
var faceVersion = "?v=1.1", //版本号
tab = $G( tabName ), //获取将要生成的Div句柄
imagePath = emotion.SmileyPath + emotion.imageFolders[tabName], //获取显示表情和预览表情的路径
positionLine = 11 / 2, //中间数
iWidth = iHeight = 35, //图片长宽
iColWidth = 3, //表格剩余空间的显示比例
tableCss = emotion.imageCss[tabName],
cssOffset = emotion.imageCssOffset[tabName],
textHTML = ['<table class="smileytable">'],
i = 0, imgNum = emotion.SmileyBox[tabName].length, imgColNum = 11, faceImage,
sUrl, realUrl, posflag, offset, infor;
for ( ; i < imgNum; ) {
textHTML.push( '<tr>' );
for ( var j = 0; j < imgColNum; j++, i++ ) {
faceImage = emotion.SmileyBox[tabName][i];
if ( faceImage ) {
sUrl = imagePath + faceImage + faceVersion;
realUrl = imagePath + faceImage;
posflag = j < positionLine ? 0 : 1;
offset = cssOffset * i * (-1) - 1;
infor = emotion.SmileyInfor[tabName][i];
textHTML.push( '<td class="' + tableCss + '" border="1" width="' + iColWidth + '%" style="border-collapse:collapse;" align="center" bgcolor="transparent" onclick="InsertSmiley(\'' + realUrl.replace( /'/g, "\\'" ) + '\',event)" onmouseover="over(this,\'' + sUrl + '\',\'' + posflag + '\')" onmouseout="out(this)">' );
textHTML.push( '<span>' );
textHTML.push( '<img style="background-position:left ' + offset + 'px;" title="' + infor + '" src="' + emotion.SmileyPath + (editor.options.emotionLocalization ? '0.gif" width="' : 'default/0.gif" width="') + iWidth + '" height="' + iHeight + '"></img>' );
textHTML.push( '</span>' );
} else {
textHTML.push( '<td width="' + iColWidth + '%" bgcolor="#FFFFFF">' );
}
textHTML.push( '</td>' );
}
textHTML.push( '</tr>' );
}
textHTML.push( '</table>' );
textHTML = textHTML.join( "" );
tab.innerHTML = textHTML;
}
function over( td, srcPath, posFlag ) {
td.style.backgroundColor = "#ACCD3C";
$G( 'faceReview' ).style.backgroundImage = "url(" + srcPath + ")";
if ( posFlag == 1 ) $G( "tabIconReview" ).className = "show";
$G( "tabIconReview" ).style.display = 'block';
}
function out( td ) {
td.style.backgroundColor = "transparent";
var tabIconRevew = $G( "tabIconReview" );
tabIconRevew.className = "";
tabIconRevew.style.display = 'none';
}
function createTabList( tabNum ) {
var obj = {};
for ( var i = 0; i < tabNum; i++ ) {
obj["tab" + i] = [];
}
return obj;
}
function createArr( tabNum ) {
var arr = [];
for ( var i = 0; i < tabNum; i++ ) {
arr[i] = 0;
}
return arr;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -0,0 +1,89 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script type="text/javascript" src="../internal.js"></script>
<style type="text/css">
.content{width:530px; height: 350px;margin: 10px auto;}
.content table{width: 100%}
.content table td{vertical-align: middle;}
#address{width:220px;height:21px;background: #FFF;border:1px solid #d7d7d7; line-height: 21px;}
</style>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
</head>
<body>
<div class="content">
<table>
<tr>
<td><label for="address"><var id="lang_input_address"></var></label></td>
<td><input id="address" type="text" /></td>
<td><a id="doSearch" href="javascript:void(0)" class="button"><var id="lang_input_search"></var></a></td>
</tr>
</table>
<div id="container" style="width: 100%; height: 340px;margin: 5px auto; border: 1px solid gray;"></div>
</div>
<script type="text/javascript">
domUtils.on(window,"load",function(){
var map = new google.maps.Map(document.getElementById('container'), {
zoom: 3,
streetViewControl: false,
scaleControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var imgcss;
var marker = new google.maps.Marker({
map: map,
draggable: true
});
function doSearch(){
var address = document.getElementById('address').value;
var geocoder = new google.maps.Geocoder();
geocoder.geocode( { 'address': address}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var bounds = results[0].geometry.viewport;
map.fitBounds(bounds);
marker.setPosition(results[0].geometry.location);
marker.setTitle(address);
} else alert(lang.searchError);
});
}
$G('address').onkeydown = function (evt){
evt = evt || event;
if (evt.keyCode == 13) {
doSearch();
}
};
$G("doSearch").onclick = doSearch;
dialog.onok = function (){
var center = map.getCenter();
var point = marker.getPosition();
var url = "http://maps.googleapis.com/maps/api/staticmap?center=" + center.lat() + ',' + center.lng() + "&zoom=" + map.zoom + "&size=520x340&maptype=" + map.getMapTypeId() + "&markers=" + point.lat() + ',' + point.lng() + "&sensor=false";
editor.execCommand('inserthtml', '<img width="520" height="340" src="' + url + '"' + (imgcss ? ' style="' + imgcss + '"' :'') + '/>');
};
function getPars(str,par){
var reg = new RegExp(par+"=((\\d+|[.,])*)","g");
return reg.exec(str)[1];
}
var img = editor.selection.getRange().getClosedNode();
if(img && img.src.indexOf("http://maps.googleapis.com/maps/api/staticmap")!=-1){
var url = img.getAttribute("src");
var centers = getPars(url,"center").split(",");
point = new google.maps.LatLng(Number(centers[0]),Number(centers[1]));
map.setCenter(point);
map.setZoom(Number(getPars(url,"zoom")));
centers = getPars(url,"markers").split(",");
marker.setPosition(new google.maps.LatLng(Number(centers[0]),Number(centers[1])));
imgcss = img.style.cssText;
}else{
setTimeout(function(){
doSearch();
},30)
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
.wrapper{width: 370px;margin: 10px auto;zoom: 1;}
.tabbody{height: 360px;}
.tabbody .panel{width:100%;height: 360px;position: absolute;background: #fff;}
.tabbody .panel h1{font-size:26px;margin: 5px 0 0 5px;}
.tabbody .panel p{font-size:12px;margin: 5px 0 0 5px;}
.tabbody table{width:90%;line-height: 20px;margin: 5px 0 0 5px;;}
.tabbody table thead{font-weight: bold;line-height: 25px;}

View File

@ -0,0 +1,82 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>帮助</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<script type="text/javascript" src="../internal.js"></script>
<link rel="stylesheet" type="text/css" href="help.css">
</head>
<body>
<div class="wrapper" id="helptab">
<div id="tabHeads" class="tabhead">
<span class="focus" tabsrc="about"><var id="lang_input_about"></var></span>
<span tabsrc="shortcuts"><var id="lang_input_shortcuts"></var></span>
</div>
<div id="tabBodys" class="tabbody">
<div id="about" class="panel">
<h1>UEditor</h1>
<p id="version"></p>
<p><var id="lang_input_introduction"></var></p>
</div>
<div id="shortcuts" class="panel">
<table>
<thead>
<tr>
<td><var id="lang_Txt_shortcuts"></var></td>
<td><var id="lang_Txt_func"></var></td>
</tr>
</thead>
<tbody>
<tr>
<td>ctrl+b</td>
<td><var id="lang_Txt_bold"></var></td>
</tr>
<tr>
<td>ctrl+c</td>
<td><var id="lang_Txt_copy"></var></td>
</tr>
<tr>
<td>ctrl+x</td>
<td><var id="lang_Txt_cut"></var></td>
</tr>
<tr>
<td>ctrl+v</td>
<td><var id="lang_Txt_Paste"></var></td>
</tr>
<tr>
<td>ctrl+y</td>
<td><var id="lang_Txt_undo"></var></td>
</tr>
<tr>
<td>ctrl+z</td>
<td><var id="lang_Txt_redo"></var></td>
</tr>
<tr>
<td>ctrl+i</td>
<td><var id="lang_Txt_italic"></var></td>
</tr>
<tr>
<td>ctrl+u</td>
<td><var id="lang_Txt_underline"></var></td>
</tr>
<tr>
<td>ctrl+a</td>
<td><var id="lang_Txt_selectAll"></var></td>
</tr>
<tr>
<td>shift+enter</td>
<td><var id="lang_Txt_visualEnter"></var></td>
</tr>
<tr>
<td>alt+z</td>
<td><var id="lang_Txt_fullscreen"></var></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<script type="text/javascript" src="help.js"></script>
</body>
</html>

View File

@ -0,0 +1,56 @@
/**
* Created with JetBrains PhpStorm.
* User: xuheng
* Date: 12-9-26
* Time: 下午1:06
* To change this template use File | Settings | File Templates.
*/
/**
* tab点击处理事件
* @param tabHeads
* @param tabBodys
* @param obj
*/
function clickHandler( tabHeads,tabBodys,obj ) {
//head样式更改
for ( var k = 0, len = tabHeads.length; k < len; k++ ) {
tabHeads[k].className = "";
}
obj.className = "focus";
//body显隐
var tabSrc = obj.getAttribute( "tabSrc" );
for ( var j = 0, length = tabBodys.length; j < length; j++ ) {
var body = tabBodys[j],
id = body.getAttribute( "id" );
body.onclick = function(){
this.style.zoom = 1;
};
if ( id != tabSrc ) {
body.style.zIndex = 1;
} else {
body.style.zIndex = 200;
}
}
}
/**
* TAB切换
* @param tabParentId tab的父节点ID或者对象本身
*/
function switchTab( tabParentId ) {
var tabElements = $G( tabParentId ).children,
tabHeads = tabElements[0].children,
tabBodys = tabElements[1].children;
for ( var i = 0, length = tabHeads.length; i < length; i++ ) {
var head = tabHeads[i];
if ( head.className === "focus" )clickHandler(tabHeads,tabBodys, head );
head.onclick = function () {
clickHandler(tabHeads,tabBodys,this);
}
}
}
switchTab("helptab");
document.getElementById('version').innerHTML = parent.UE.version;

View File

@ -0,0 +1,894 @@
@charset "utf-8";
/* dialog样式 */
.wrapper {
zoom: 1;
width: 630px;
*width: 626px;
height: 380px;
margin: 0 auto;
padding: 10px;
position: relative;
font-family: sans-serif;
}
/*tab样式框大小*/
.tabhead {
float:left;
}
.tabbody {
width: 100%;
height: 346px;
position: relative;
clear: both;
}
.tabbody .panel {
position: absolute;
width: 0;
height: 0;
background: #fff;
overflow: hidden;
display: none;
}
.tabbody .panel.focus {
width: 100%;
height: 346px;
display: block;
}
/* 图片对齐方式 */
.alignBar{
float:right;
margin-top: 5px;
position: relative;
}
.alignBar .algnLabel{
float:left;
height: 20px;
line-height: 20px;
}
.alignBar #alignIcon{
zoom:1;
_display: inline;
display: inline-block;
position: relative;
}
.alignBar #alignIcon span{
float: left;
cursor: pointer;
display: block;
width: 19px;
height: 17px;
margin-right: 3px;
margin-left: 3px;
background-image: url(./images/alignicon.jpg);
}
.alignBar #alignIcon .none-align{
background-position: 0 -18px;
}
.alignBar #alignIcon .left-align{
background-position: -20px -18px;
}
.alignBar #alignIcon .right-align{
background-position: -40px -18px;
}
.alignBar #alignIcon .center-align{
background-position: -60px -18px;
}
.alignBar #alignIcon .none-align.focus{
background-position: 0 0;
}
.alignBar #alignIcon .left-align.focus{
background-position: -20px 0;
}
.alignBar #alignIcon .right-align.focus{
background-position: -40px 0;
}
.alignBar #alignIcon .center-align.focus{
background-position: -60px 0;
}
/* 远程图片样式 */
#remote {
z-index: 200;
}
#remote .top{
width: 100%;
margin-top: 25px;
}
#remote .left{
display: block;
float: left;
width: 300px;
height:10px;
}
#remote .right{
display: block;
float: right;
width: 300px;
height:10px;
}
#remote .row{
margin-left: 20px;
clear: both;
height: 40px;
}
#remote .row label{
text-align: center;
width: 50px;
zoom:1;
_display: inline;
display:inline-block;
vertical-align: middle;
}
#remote .row label.algnLabel{
float: left;
}
#remote input.text{
width: 150px;
padding: 3px 6px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
-webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
}
#remote input.text:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
}
#remote #url{
width: 500px;
margin-bottom: 2px;
}
#remote #width,
#remote #height{
width: 20px;
margin-left: 2px;
margin-right: 2px;
}
#remote #border,
#remote #vhSpace,
#remote #title{
width: 180px;
margin-right: 5px;
}
#remote #lock{
}
#remote #lockicon{
zoom: 1;
_display:inline;
display: inline-block;
width: 20px;
height: 20px;
background: url("../../themes/default/images/lock.gif") -13px -13px no-repeat;
vertical-align: middle;
}
#remote #preview{
clear: both;
width: 260px;
height: 240px;
z-index: 9999;
margin-top: 10px;
background-color: #eee;
overflow: hidden;
}
/* 上传图片 */
.tabbody #upload.panel {
width: 0;
height: 0;
overflow: hidden;
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
background: #fff;
display: block;
}
.tabbody #upload.panel.focus {
width: 100%;
height: 346px;
display: block;
clip: auto;
}
#upload .queueList {
margin: 0;
width: 100%;
height: 100%;
position: absolute;
overflow: hidden;
}
#upload p {
margin: 0;
}
.element-invisible {
width: 0 !important;
height: 0 !important;
border: 0;
padding: 0;
margin: 0;
overflow: hidden;
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
}
#upload .placeholder {
margin: 10px;
border: 2px dashed #e6e6e6;
*border: 0px dashed #e6e6e6;
height: 172px;
padding-top: 150px;
text-align: center;
background: url(./images/image.png) center 70px no-repeat;
color: #cccccc;
font-size: 18px;
position: relative;
top:0;
*top: 10px;
}
#upload .placeholder .webuploader-pick {
font-size: 18px;
background: #00b7ee;
border-radius: 3px;
line-height: 44px;
padding: 0 30px;
*width: 120px;
color: #fff;
display: inline-block;
margin: 0 auto 20px auto;
cursor: pointer;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
}
#upload .placeholder .webuploader-pick-hover {
background: #00a2d4;
}
#filePickerContainer {
text-align: center;
}
#upload .placeholder .flashTip {
color: #666666;
font-size: 12px;
position: absolute;
width: 100%;
text-align: center;
bottom: 20px;
}
#upload .placeholder .flashTip a {
color: #0785d1;
text-decoration: none;
}
#upload .placeholder .flashTip a:hover {
text-decoration: underline;
}
#upload .placeholder.webuploader-dnd-over {
border-color: #999999;
}
#upload .filelist {
list-style: none;
margin: 0;
padding: 0;
overflow-x: hidden;
overflow-y: auto;
position: relative;
height: 300px;
}
#upload .filelist:after {
content: '';
display: block;
width: 0;
height: 0;
overflow: hidden;
clear: both;
position: relative;
}
#upload .filelist li {
width: 113px;
height: 113px;
background: url(./images/bg.png);
text-align: center;
margin: 9px 0 0 9px;
*margin: 6px 0 0 6px;
position: relative;
display: block;
float: left;
overflow: hidden;
font-size: 12px;
}
#upload .filelist li p.log {
position: relative;
top: -45px;
}
#upload .filelist li p.title {
position: absolute;
top: 0;
left: 0;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
top: 5px;
text-indent: 5px;
text-align: left;
}
#upload .filelist li p.progress {
position: absolute;
width: 100%;
bottom: 0;
left: 0;
height: 8px;
overflow: hidden;
z-index: 50;
margin: 0;
border-radius: 0;
background: none;
-webkit-box-shadow: 0 0 0;
}
#upload .filelist li p.progress span {
display: none;
overflow: hidden;
width: 0;
height: 100%;
background: #1483d8 url(./images/progress.png) repeat-x;
-webit-transition: width 200ms linear;
-moz-transition: width 200ms linear;
-o-transition: width 200ms linear;
-ms-transition: width 200ms linear;
transition: width 200ms linear;
-webkit-animation: progressmove 2s linear infinite;
-moz-animation: progressmove 2s linear infinite;
-o-animation: progressmove 2s linear infinite;
-ms-animation: progressmove 2s linear infinite;
animation: progressmove 2s linear infinite;
-webkit-transform: translateZ(0);
}
@-webkit-keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
@-moz-keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
@keyframes progressmove {
0% {
background-position: 0 0;
}
100% {
background-position: 17px 0;
}
}
#upload .filelist li p.imgWrap {
position: relative;
z-index: 2;
line-height: 113px;
vertical-align: middle;
overflow: hidden;
width: 113px;
height: 113px;
-webkit-transform-origin: 50% 50%;
-moz-transform-origin: 50% 50%;
-o-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
transform-origin: 50% 50%;
-webit-transition: 200ms ease-out;
-moz-transition: 200ms ease-out;
-o-transition: 200ms ease-out;
-ms-transition: 200ms ease-out;
transition: 200ms ease-out;
}
#upload .filelist li img {
width: 100%;
}
#upload .filelist li p.error {
background: #f43838;
color: #fff;
position: absolute;
bottom: 0;
left: 0;
height: 28px;
line-height: 28px;
width: 100%;
z-index: 100;
display:none;
}
#upload .filelist li .success {
display: block;
position: absolute;
left: 0;
bottom: 0;
height: 40px;
width: 100%;
z-index: 200;
background: url(./images/success.png) no-repeat right bottom;
background: url(./images/success.gif) no-repeat right bottom \9;
}
#upload .filelist li.filePickerBlock {
width: 113px;
height: 113px;
background: url(./images/image.png) no-repeat center 12px;
border: 1px solid #eeeeee;
border-radius: 0;
}
#upload .filelist li.filePickerBlock div.webuploader-pick {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
opacity: 0;
background: none;
font-size: 0;
}
#upload .filelist div.file-panel {
position: absolute;
height: 0;
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \0;
background: rgba(0, 0, 0, 0.5);
width: 100%;
top: 0;
left: 0;
overflow: hidden;
z-index: 300;
}
#upload .filelist div.file-panel span {
width: 24px;
height: 24px;
display: inline;
float: right;
text-indent: -9999px;
overflow: hidden;
background: url(./images/icons.png) no-repeat;
background: url(./images/icons.gif) no-repeat \9;
margin: 5px 1px 1px;
cursor: pointer;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#upload .filelist div.file-panel span.rotateLeft {
display:none;
background-position: 0 -24px;
}
#upload .filelist div.file-panel span.rotateLeft:hover {
background-position: 0 0;
}
#upload .filelist div.file-panel span.rotateRight {
display:none;
background-position: -24px -24px;
}
#upload .filelist div.file-panel span.rotateRight:hover {
background-position: -24px 0;
}
#upload .filelist div.file-panel span.cancel {
background-position: -48px -24px;
}
#upload .filelist div.file-panel span.cancel:hover {
background-position: -48px 0;
}
#upload .statusBar {
height: 45px;
border-bottom: 1px solid #dadada;
margin: 0 10px;
padding: 0;
line-height: 45px;
vertical-align: middle;
position: relative;
}
#upload .statusBar .progress {
border: 1px solid #1483d8;
width: 198px;
background: #fff;
height: 18px;
position: absolute;
top: 12px;
display: none;
text-align: center;
line-height: 18px;
color: #6dbfff;
margin: 0 10px 0 0;
}
#upload .statusBar .progress span.percentage {
width: 0;
height: 100%;
left: 0;
top: 0;
background: #1483d8;
position: absolute;
}
#upload .statusBar .progress span.text {
position: relative;
z-index: 10;
}
#upload .statusBar .info {
display: inline-block;
font-size: 14px;
color: #666666;
}
#upload .statusBar .btns {
position: absolute;
top: 7px;
right: 0;
line-height: 30px;
}
#filePickerBtn {
display: inline-block;
float: left;
}
#upload .statusBar .btns .webuploader-pick,
#upload .statusBar .btns .uploadBtn,
#upload .statusBar .btns .uploadBtn.state-uploading,
#upload .statusBar .btns .uploadBtn.state-paused {
background: #ffffff;
border: 1px solid #cfcfcf;
color: #565656;
padding: 0 18px;
display: inline-block;
border-radius: 3px;
margin-left: 10px;
cursor: pointer;
font-size: 14px;
float: left;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#upload .statusBar .btns .webuploader-pick-hover,
#upload .statusBar .btns .uploadBtn:hover,
#upload .statusBar .btns .uploadBtn.state-uploading:hover,
#upload .statusBar .btns .uploadBtn.state-paused:hover {
background: #f0f0f0;
}
#upload .statusBar .btns .uploadBtn,
#upload .statusBar .btns .uploadBtn.state-paused{
background: #00b7ee;
color: #fff;
border-color: transparent;
}
#upload .statusBar .btns .uploadBtn:hover,
#upload .statusBar .btns .uploadBtn.state-paused:hover{
background: #00a2d4;
}
#upload .statusBar .btns .uploadBtn.disabled {
pointer-events: none;
filter:alpha(opacity=60);
-moz-opacity:0.6;
-khtml-opacity: 0.6;
opacity: 0.6;
}
/* 图片管理样式 */
#online {
width: 100%;
height: 336px;
padding: 10px 0 0 0;
}
#online #imageList{
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
position: relative;
}
#online ul {
display: block;
list-style: none;
margin: 0;
padding: 0;
}
#online li {
float: left;
display: block;
list-style: none;
padding: 0;
width: 113px;
height: 113px;
margin: 0 0 9px 9px;
*margin: 0 0 6px 6px;
background-color: #eee;
overflow: hidden;
cursor: pointer;
position: relative;
}
#online li.clearFloat {
float: none;
clear: both;
display: block;
width:0;
height:0;
margin: 0;
padding: 0;
}
#online li img {
cursor: pointer;
}
#online li .icon {
cursor: pointer;
width: 113px;
height: 113px;
position: absolute;
top: 0;
left: 0;
z-index: 2;
border: 0;
background-repeat: no-repeat;
}
#online li .icon:hover {
width: 107px;
height: 107px;
border: 3px solid #1094fa;
}
#online li.selected .icon {
background-image: url(images/success.png);
background-image: url(images/success.gif)\9;
background-position: 75px 75px;
}
#online li.selected .icon:hover {
width: 107px;
height: 107px;
border: 3px solid #1094fa;
background-position: 72px 72px;
}
/* 图片搜索样式 */
#search .searchBar {
width: 100%;
height: 30px;
margin: 10px 0 5px 0;
padding: 0;
}
#search input.text{
width: 150px;
padding: 3px 6px;
font-size: 14px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
-webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
}
#search input.text:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
}
#search input.searchTxt {
margin-left:5px;
padding-left: 5px;
background: #FFF;
width: 300px;
*width: 260px;
height: 21px;
line-height: 21px;
float: left;
dislay: block;
}
#search .searchType {
width: 65px;
height: 28px;
padding:0;
line-height: 28px;
border: 1px solid #d7d7d7;
border-radius: 0;
vertical-align: top;
margin-left: 5px;
float: left;
dislay: block;
}
#search #searchBtn,
#search #searchReset {
display: inline-block;
margin-bottom: 0;
margin-right: 5px;
padding: 4px 10px;
font-weight: 400;
text-align: center;
vertical-align: middle;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
font-size: 14px;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
vertical-align: top;
float: right;
}
#search #searchBtn {
color: white;
border-color: #285e8e;
background-color: #3b97d7;
}
#search #searchReset {
color: #333;
border-color: #ccc;
background-color: #fff;
}
#search #searchBtn:hover {
background-color: #3276b1;
}
#search #searchReset:hover {
background-color: #eee;
}
#search .msg {
margin-left: 5px;
}
#search .searchList{
width: 100%;
height: 300px;
overflow: hidden;
clear: both;
}
#search .searchList ul{
margin:0;
padding:0;
list-style:none;
clear: both;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: auto;
zoom: 1;
position: relative;
}
#search .searchList li {
list-style:none;
float: left;
display: block;
width: 115px;
margin: 5px 10px 5px 20px;
*margin: 5px 10px 5px 15px;
padding:0;
font-size: 12px;
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
-moz-box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
position: relative;
vertical-align: top;
text-align: center;
overflow: hidden;
cursor: pointer;
filter: alpha(Opacity=100);
-moz-opacity: 1;
opacity: 1;
border: 2px solid #eee;
}
#search .searchList li.selected {
filter: alpha(Opacity=40);
-moz-opacity: 0.4;
opacity: 0.4;
border: 2px solid #00a0e9;
}
#search .searchList li p {
background-color: #eee;
margin: 0;
padding: 0;
position: relative;
width:100%;
height:115px;
overflow: hidden;
}
#search .searchList li p img {
cursor: pointer;
border: 0;
}
#search .searchList li a {
color: #999;
border-top: 1px solid #F2F2F2;
background: #FAFAFA;
text-align: center;
display: block;
padding: 0 5px;
width: 105px;
height:32px;
line-height:32px;
white-space:nowrap;
text-overflow:ellipsis;
text-decoration: none;
overflow: hidden;
word-break: break-all;
}
#search .searchList a:hover {
text-decoration: underline;
color: #333;
}
#search .searchList .clearFloat{
clear: both;
}

View File

@ -0,0 +1,120 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>ueditor图片对话框</title>
<script type="text/javascript" src="../internal.js"></script>
<!-- jquery -->
<script type="text/javascript" src="../../third-party/jquery-1.10.2.min.js"></script>
<!-- webuploader -->
<script src="../../third-party/webuploader/webuploader.min.js"></script>
<link rel="stylesheet" type="text/css" href="../../third-party/webuploader/webuploader.css">
<!-- image dialog -->
<link rel="stylesheet" href="image.css" type="text/css" />
</head>
<body>
<div class="wrapper">
<div id="tabhead" class="tabhead">
<span class="tab" data-content-id="remote"><var id="lang_tab_remote"></var></span>
<span class="tab focus" data-content-id="upload"><var id="lang_tab_upload"></var></span>
<span class="tab" data-content-id="online"><var id="lang_tab_online"></var></span>
<span class="tab" data-content-id="search"><var id="lang_tab_search"></var></span>
</div>
<div class="alignBar">
<label class="algnLabel"><var id="lang_input_align"></var></label>
<span id="alignIcon">
<span id="noneAlign" class="none-align focus" data-align="none"></span>
<span id="leftAlign" class="left-align" data-align="left"></span>
<span id="rightAlign" class="right-align" data-align="right"></span>
<span id="centerAlign" class="center-align" data-align="center"></span>
</span>
<input id="align" name="align" type="hidden" value="none"/>
</div>
<div id="tabbody" class="tabbody">
<!-- 远程图片 -->
<div id="remote" class="panel">
<div class="top">
<div class="row">
<label for="url"><var id="lang_input_url"></var></label>
<span><input class="text" id="url" type="text"/></span>
</div>
</div>
<div class="left">
<div class="row">
<label><var id="lang_input_size"></var></label>
<span><var id="lang_input_width">&nbsp;&nbsp;</var><input class="text" type="text" id="width"/>px </span>
<span><var id="lang_input_height">&nbsp;&nbsp;</var><input class="text" type="text" id="height"/>px </span>
<span><input id="lock" type="checkbox" disabled="disabled"><span id="lockicon"></span></span>
</div>
<div class="row">
<label><var id="lang_input_border"></var></label>
<span><input class="text" type="text" id="border"/>px </span>
</div>
<div class="row">
<label><var id="lang_input_vhspace"></var></label>
<span><input class="text" type="text" id="vhSpace"/>px </span>
</div>
<div class="row">
<label><var id="lang_input_title"></var></label>
<span><input class="text" type="text" id="title"/></span>
</div>
</div>
<div class="right"><div id="preview"></div></div>
</div>
<!-- 上传图片 -->
<div id="upload" class="panel focus">
<div id="queueList" class="queueList">
<div class="statusBar element-invisible">
<div class="progress">
<span class="text">0%</span>
<span class="percentage"></span>
</div><div class="info"></div>
<div class="btns">
<div id="filePickerBtn"></div>
<div class="uploadBtn"><var id="lang_start_upload"></var></div>
</div>
</div>
<div id="dndArea" class="placeholder">
<div class="filePickerContainer">
<div id="filePickerReady"></div>
</div>
</div>
<ul class="filelist element-invisible">
<li id="filePickerBlock" class="filePickerBlock"></li>
</ul>
</div>
</div>
<!-- 在线图片 -->
<div id="online" class="panel">
<div id="imageList"><var id="lang_imgLoading"></var></div>
</div>
<!-- 搜索图片 -->
<div id="search" class="panel">
<div class="searchBar">
<input id="searchTxt" class="searchTxt text" type="text" />
<select id="searchType" class="searchType">
<option value="&s=4&z=0"></option>
<option value="&s=1&z=19"></option>
<option value="&s=2&z=0"></option>
<option value="&s=3&z=0"></option>
</select>
<input id="searchReset" type="button" />
<input id="searchBtn" type="button" />
</div>
<div id="searchList" class="searchList"><ul id="searchListUl"></ul></div>
</div>
</div>
</div>
<script type="text/javascript" src="image.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script type="text/javascript" src="../internal.js"></script>
<style type="text/css">
.warp {width: 320px;height: 153px;margin-left:5px;padding: 20px 0 0 15px;position: relative;}
#url {width: 290px; margin-bottom: 2px; margin-left: -6px; margin-left: -2px\9;*margin-left:0;_margin-left:0; }
.format span{display: inline-block; width: 58px;text-align: center; zoom:1;}
table td{padding:5px 0;}
#align{width: 65px;height: 23px;line-height: 22px;}
</style>
</head>
<body>
<div class="warp">
<table width="300" cellpadding="0" cellspacing="0">
<tr>
<td colspan="2" class="format">
<span><var id="lang_input_address"></var></span>
<input style="width:200px" id="url" type="text" value=""/>
</td>
</tr>
<tr>
<td colspan="2" class="format"><span><var id="lang_input_width"></var></span><input style="width:200px" type="text" id="width"/> px</td>
</tr>
<tr>
<td colspan="2" class="format"><span><var id="lang_input_height"></var></span><input style="width:200px" type="text" id="height"/> px</td>
</tr>
<tr>
<td><span><var id="lang_input_isScroll"></var></span><input type="checkbox" id="scroll"/> </td>
<td><span><var id="lang_input_frameborder"></var></span><input type="checkbox" id="frameborder"/> </td>
</tr>
<tr>
<td colspan="2"><span><var id="lang_input_alignMode"></var></span>
<select id="align">
<option value=""></option>
<option value="left"></option>
<option value="right"></option>
</select>
</td>
</tr>
</table>
</div>
<script type="text/javascript">
var iframe = editor._iframe;
if(iframe){
$G("url").value = iframe.getAttribute("src")||"";
$G("width").value = iframe.getAttribute("width")||iframe.style.width.replace("px","")||"";
$G("height").value = iframe.getAttribute("height") || iframe.style.height.replace("px","") ||"";
$G("scroll").checked = (iframe.getAttribute("scrolling") == "yes") ? true : false;
$G("frameborder").checked = (iframe.getAttribute("frameborder") == "1") ? true : false;
$G("align").value = iframe.align ? iframe.align : "";
}
function queding(){
var url = $G("url").value.replace(/^\s*|\s*$/ig,""),
width = $G("width").value,
height = $G("height").value,
scroll = $G("scroll"),
frameborder = $G("frameborder"),
float = $G("align").value,
newIframe = editor.document.createElement("iframe"),
div;
if(!url){
alert(lang.enterAddress);
return false;
}
newIframe.setAttribute("src",/http:\/\/|https:\/\//ig.test(url) ? url : "http://"+url);
/^[1-9]+[.]?\d*$/g.test( width ) ? newIframe.setAttribute("width",width) : "";
/^[1-9]+[.]?\d*$/g.test( height ) ? newIframe.setAttribute("height",height) : "";
scroll.checked ? newIframe.setAttribute("scrolling","yes") : newIframe.setAttribute("scrolling","no");
frameborder.checked ? newIframe.setAttribute("frameborder","1",0) : newIframe.setAttribute("frameborder","0",0);
float ? newIframe.setAttribute("align",float) : newIframe.setAttribute("align","");
if(iframe){
iframe.parentNode.insertBefore(newIframe,iframe);
domUtils.remove(iframe);
}else{
div = editor.document.createElement("div");
div.appendChild(newIframe);
editor.execCommand("inserthtml",div.innerHTML);
}
editor._iframe = null;
dialog.close();
}
dialog.onok = queding;
$G("url").onkeydown = function(evt){
evt = evt || event;
if(evt.keyCode == 13){
queding();
}
};
$focus($G( "url" ));
</script>
</body>
</html>

View File

@ -0,0 +1,81 @@
(function () {
var parent = window.parent;
//dialog对象
dialog = parent.$EDITORUI[window.frameElement.id.replace( /_iframe$/, '' )];
//当前打开dialog的编辑器实例
editor = dialog.editor;
UE = parent.UE;
domUtils = UE.dom.domUtils;
utils = UE.utils;
browser = UE.browser;
ajax = UE.ajax;
$G = function ( id ) {
return document.getElementById( id )
};
//focus元素
$focus = function ( node ) {
setTimeout( function () {
if ( browser.ie ) {
var r = node.createTextRange();
r.collapse( false );
r.select();
} else {
node.focus()
}
}, 0 )
};
utils.loadFile(document,{
href:editor.options.themePath + editor.options.theme + "/dialogbase.css?cache="+Math.random(),
tag:"link",
type:"text/css",
rel:"stylesheet"
});
lang = editor.getLang(dialog.className.split( "-" )[2]);
if(lang){
domUtils.on(window,'load',function () {
var langImgPath = editor.options.langPath + editor.options.lang + "/images/";
//针对静态资源
for ( var i in lang["static"] ) {
var dom = $G( i );
if(!dom) continue;
var tagName = dom.tagName,
content = lang["static"][i];
if(content.src){
//clone
content = utils.extend({},content,false);
content.src = langImgPath + content.src;
}
if(content.style){
content = utils.extend({},content,false);
content.style = content.style.replace(/url\s*\(/g,"url(" + langImgPath)
}
switch ( tagName.toLowerCase() ) {
case "var":
dom.parentNode.replaceChild( document.createTextNode( content ), dom );
break;
case "select":
var ops = dom.options;
for ( var j = 0, oj; oj = ops[j]; ) {
oj.innerHTML = content.options[j++];
}
for ( var p in content ) {
p != "options" && dom.setAttribute( p, content[p] );
}
break;
default :
domUtils.setAttributes( dom, content);
}
}
} );
}
})();

View File

@ -0,0 +1,126 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<script type="text/javascript" src="../internal.js"></script>
<style type="text/css">
*{margin:0;padding:0;color: #838383;}
table{font-size: 12px;margin: 10px;line-height: 30px}
.txt{width:300px;height:21px;line-height:21px;border:1px solid #d7d7d7;}
</style>
</head>
<body>
<table>
<tr>
<td><label for="text"> <var id="lang_input_text"></var></label></td>
<td><input class="txt" id="text" type="text" disabled="true"/></td>
</tr>
<tr>
<td><label for="href"> <var id="lang_input_url"></var></label></td>
<td><input class="txt" id="href" type="text" /></td>
</tr>
<tr>
<td><label for="title"> <var id="lang_input_title"></var></label></td>
<td><input class="txt" id="title" type="text"/></td>
</tr>
<tr>
<td colspan="2">
<label for="target"><var id="lang_input_target"></var></label>
<input id="target" type="checkbox"/>
</td>
</tr>
<tr>
<td colspan="2" id="msg"></td>
</tr>
</table>
<script type="text/javascript">
var range = editor.selection.getRange(),
link = range.collapsed ? editor.queryCommandValue( "link" ) : editor.selection.getStart(),
url,
text = $G('text'),
rangeLink = domUtils.findParentByTagName(range.getCommonAncestor(),'a',true),
orgText;
link = domUtils.findParentByTagName( link, "a", true );
if(link){
url = utils.html(link.getAttribute( '_href' ) || link.getAttribute( 'href', 2 ));
if(rangeLink === link && !link.getElementsByTagName('img').length){
text.removeAttribute('disabled');
orgText = text.value = link[browser.ie ? 'innerText':'textContent'];
}else{
text.setAttribute('disabled','true');
text.value = lang.validLink;
}
}else{
if(range.collapsed){
text.removeAttribute('disabled');
text.value = '';
}else{
text.setAttribute('disabled','true');
text.value = lang.validLink;
}
}
$G("title").value = url ? link.title : "";
$G("href").value = url ? url: '';
$G("target").checked = url && link.target == "_blank" ? true : false;
$focus($G("href"));
function handleDialogOk(){
var href =$G('href').value.replace(/^\s+|\s+$/g, '');
if(href){
if(!hrefStartWith(href,["http","/","ftp://",'#'])) {
href = "http://" + href;
}
var obj = {
'href' : href,
'target' : $G("target").checked ? "_blank" : '_self',
'title' : $G("title").value.replace(/^\s+|\s+$/g, ''),
'_href':href
};
//修改链接内容的情况太特殊了所以先做到这里了
//todo:情况多的时候做到command里
if(orgText && text.value != orgText){
link[browser.ie ? 'innerText' : 'textContent'] = obj.textValue = text.value;
range.selectNode(link).select()
}
if(range.collapsed){
obj.textValue = text.value;
}
editor.execCommand('link',utils.clearEmptyAttrs(obj) );
dialog.close();
}
}
dialog.onok = handleDialogOk;
$G('href').onkeydown = $G('title').onkeydown = function(evt){
evt = evt || window.event;
if (evt.keyCode == 13) {
handleDialogOk();
return false;
}
};
$G('href').onblur = function(){
if(!hrefStartWith(this.value,["http","/","ftp://",'#'])){
$G("msg").innerHTML = "<span style='color: red'>"+lang.httpPrompt+"</span>";
}else{
$G("msg").innerHTML = "";
}
};
function hrefStartWith(href,arr){
href = href.replace(/^\s+|\s+$/g, '');
for(var i=0,ai;ai=arr[i++];){
if(href.indexOf(ai)==0){
return true;
}
}
return false;
}
</script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More