diff --git a/apps/application/chat_pipeline/step/chat_step/i_chat_step.py b/apps/application/chat_pipeline/step/chat_step/i_chat_step.py index f18fbc211..fe4bc1dfd 100644 --- a/apps/application/chat_pipeline/step/chat_step/i_chat_step.py +++ b/apps/application/chat_pipeline/step/chat_step/i_chat_step.py @@ -44,7 +44,7 @@ class PostResponseHandler: @abstractmethod def handler(self, chat_id, chat_record_id, paragraph_list: List[ParagraphPipelineModel], problem_text: str, answer_text, - manage, step, padding_problem_text: str = None, **kwargs): + manage, step, padding_problem_text: str = None, client_id=None, **kwargs): pass diff --git a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py b/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py index 442f359d0..808b43de3 100644 --- a/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py +++ b/apps/application/chat_pipeline/step/chat_step/impl/base_chat_step.py @@ -67,7 +67,7 @@ def event_content(response, manage.context['message_tokens'] = manage.context['message_tokens'] + request_token manage.context['answer_tokens'] = manage.context['answer_tokens'] + response_token post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, - all_text, manage, step, padding_problem_text) + all_text, manage, step, padding_problem_text, client_id) yield 'data: ' + json.dumps({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': '', 'is_end': True}) + "\n\n" add_access_num(client_id, client_type) @@ -172,7 +172,7 @@ class BaseChatStep(IChatStep): manage.context['message_tokens'] = manage.context['message_tokens'] + request_token manage.context['answer_tokens'] = manage.context['answer_tokens'] + response_token post_response_handler.handler(chat_id, chat_record_id, paragraph_list, problem_text, - chat_result.content, manage, self, padding_problem_text) + chat_result.content, manage, self, padding_problem_text, client_id) add_access_num(client_id, client_type) return result.success({'chat_id': str(chat_id), 'id': str(chat_record_id), 'operate': True, 'content': chat_result.content, 'is_end': True}) diff --git a/apps/application/migrations/0002_chat_client_id.py b/apps/application/migrations/0002_chat_client_id.py new file mode 100644 index 000000000..37d900ef4 --- /dev/null +++ b/apps/application/migrations/0002_chat_client_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-03-28 13:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='chat', + name='client_id', + field=models.UUIDField(default=None, null=True, verbose_name='客户端id'), + ), + ] diff --git a/apps/application/models/application.py b/apps/application/models/application.py index ecd884c2b..e20e92385 100644 --- a/apps/application/models/application.py +++ b/apps/application/models/application.py @@ -69,6 +69,7 @@ class Chat(AppModelMixin): id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id") application = models.ForeignKey(Application, on_delete=models.CASCADE) abstract = models.CharField(max_length=256, verbose_name="摘要") + client_id = models.UUIDField(verbose_name="客户端id", default=None, null=True) class Meta: db_table = "application_chat" diff --git a/apps/application/serializers/application_statistics_serializers.py b/apps/application/serializers/application_statistics_serializers.py new file mode 100644 index 000000000..e958cb34d --- /dev/null +++ b/apps/application/serializers/application_statistics_serializers.py @@ -0,0 +1,128 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: application_statistics_serializers.py + @date:2024/3/27 10:55 + @desc: +""" +import datetime +import os +from typing import List, Dict + +from django.db import models +from django.db.models.query import QuerySet +from rest_framework import serializers + +from application.models.api_key_model import ApplicationPublicAccessClient +from common.db.search import native_search, get_dynamics_model +from common.util.field_message import ErrMessage +from common.util.file_util import get_file_content +from smartdoc.conf import PROJECT_DIR + + +class ApplicationStatisticsSerializer(serializers.Serializer): + application_id = serializers.UUIDField(required=True, error_messages=ErrMessage.char("应用id")) + start_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("开始时间")) + end_time = serializers.DateField(format='%Y-%m-%d', error_messages=ErrMessage.date("结束时间")) + + def get_end_time(self): + return datetime.datetime.combine( + datetime.datetime.strptime(self.data.get('end_time'), '%Y-%m-%d'), + datetime.datetime.max.time()) + + def get_start_time(self): + return self.data.get('start_time') + + def get_customer_count(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + start_time = self.get_start_time() + end_time = self.get_end_time() + return native_search( + QuerySet(ApplicationPublicAccessClient).filter(application_id=self.data.get('application_id'), + create_time__gte=start_time, + create_time__lte=end_time), + select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'customer_count.sql')), + with_search_one=True) + + def get_customer_count_trend(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + start_time = self.get_start_time() + end_time = self.get_end_time() + return native_search( + {'default_sql': QuerySet(ApplicationPublicAccessClient).filter( + application_id=self.data.get('application_id'), + create_time__gte=start_time, + create_time__lte=end_time)}, + select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'customer_count_trend.sql'))) + + def get_chat_record_aggregate(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + start_time = self.get_start_time() + end_time = self.get_end_time() + chat_record_aggregate = native_search( + QuerySet(model=get_dynamics_model( + {'application_chat.application_id': models.UUIDField(), + 'application_chat_record.create_time': models.DateTimeField()})).filter( + **{'application_chat.application_id': self.data.get('application_id'), + 'application_chat_record.create_time__gte': start_time, + 'application_chat_record.create_time__lte': end_time} + ), + select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'chat_record_count.sql')), + with_search_one=True) + customer = self.get_customer_count(with_valid=False) + return {**chat_record_aggregate, **customer} + + def get_chat_record_aggregate_trend(self, with_valid=True): + if with_valid: + self.is_valid(raise_exception=True) + start_time = self.get_start_time() + end_time = self.get_end_time() + chat_record_aggregate_trend = native_search( + {'default_sql': QuerySet(model=get_dynamics_model( + {'application_chat.application_id': models.UUIDField(), + 'application_chat_record.create_time': models.DateTimeField()})).filter( + **{'application_chat.application_id': self.data.get('application_id'), + 'application_chat_record.create_time__gte': start_time, + 'application_chat_record.create_time__lte': end_time} + )}, + select_string=get_file_content( + os.path.join(PROJECT_DIR, "apps", "application", 'sql', 'chat_record_count_trend.sql'))) + customer_count_trend = self.get_customer_count_trend(with_valid=False) + return self.merge_customer_chat_record(chat_record_aggregate_trend, customer_count_trend) + + def merge_customer_chat_record(self, chat_record_aggregate_trend: List[Dict], customer_count_trend: List[Dict]): + + return [{**self.find(chat_record_aggregate_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day, + {'star_num': 0, 'trample_num': 0, 'tokens_num': 0, 'chat_record_count': 0, + 'customer_num': 0, + 'day': day}), + **self.find(customer_count_trend, lambda c: c.get('day').strftime('%Y-%m-%d') == day, + {'customer_added_count': 0})} + for + day in + self.get_days_between_dates(self.data.get('start_time'), self.data.get('end_time'))] + + @staticmethod + def find(source_list, condition, default): + value_list = [row for row in source_list if condition(row)] + if len(value_list) > 0: + return value_list[0] + return default + + @staticmethod + def get_days_between_dates(start_date, end_date): + start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d') + end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d') + days = [] + current_date = start_date + while current_date <= end_date: + days.append(current_date.strftime('%Y-%m-%d')) + current_date += datetime.timedelta(days=1) + return days diff --git a/apps/application/serializers/chat_message_serializers.py b/apps/application/serializers/chat_message_serializers.py index d94f633f4..cb40dad86 100644 --- a/apps/application/serializers/chat_message_serializers.py +++ b/apps/application/serializers/chat_message_serializers.py @@ -87,13 +87,14 @@ class ChatInfo: 'exclude_paragraph_id_list': exclude_paragraph_id_list, 'stream': stream, 'client_id': client_id, 'client_type': client_type} - def append_chat_record(self, chat_record: ChatRecord): + def append_chat_record(self, chat_record: ChatRecord, client_id=None): # 存入缓存中 self.chat_record_list.append(chat_record) if self.application.id is not None: # 插入数据库 if not QuerySet(Chat).filter(id=self.chat_id).exists(): - Chat(id=self.chat_id, application_id=self.application.id, abstract=chat_record.problem_text).save() + Chat(id=self.chat_id, application_id=self.application.id, abstract=chat_record.problem_text, + client_id=client_id).save() # 插入会话记录 chat_record.save() @@ -110,6 +111,7 @@ def get_post_handler(chat_info: ChatInfo): manage: PiplineManage, step: BaseChatStep, padding_problem_text: str = None, + client_id=None, **kwargs): chat_record = ChatRecord(id=chat_record_id, chat_id=chat_id, @@ -120,7 +122,7 @@ def get_post_handler(chat_info: ChatInfo): answer_tokens=manage.context['answer_tokens'], run_time=manage.context['run_time'], index=len(chat_info.chat_record_list) + 1) - chat_info.append_chat_record(chat_record) + chat_info.append_chat_record(chat_record, client_id) # 重新设置缓存 chat_cache.set(chat_id, chat_info, timeout=60 * 30) diff --git a/apps/application/sql/chat_record_count.sql b/apps/application/sql/chat_record_count.sql new file mode 100644 index 000000000..0cdbfb9d2 --- /dev/null +++ b/apps/application/sql/chat_record_count.sql @@ -0,0 +1,9 @@ +SELECT SUM + ( CASE WHEN application_chat_record.vote_status = '0' THEN 1 ELSE 0 END ) AS "star_num", + SUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS "trample_num", + SUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as "tokens_num", + "count"(DISTINCT application_chat.client_id) customer_num, + "count"(application_chat_record."id") as chat_record_count +FROM + application_chat_record application_chat_record + LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id \ No newline at end of file diff --git a/apps/application/sql/chat_record_count_trend.sql b/apps/application/sql/chat_record_count_trend.sql new file mode 100644 index 000000000..b4499f0e2 --- /dev/null +++ b/apps/application/sql/chat_record_count_trend.sql @@ -0,0 +1,12 @@ +SELECT SUM + ( CASE WHEN application_chat_record.vote_status = '0' THEN 1 ELSE 0 END ) AS "star_num", + SUM ( CASE WHEN application_chat_record.vote_status = '1' THEN 1 ELSE 0 END ) AS "trample_num", + SUM ( application_chat_record.message_tokens + application_chat_record.answer_tokens ) as "tokens_num", + "count"(application_chat_record."id") as chat_record_count, + "count"(DISTINCT application_chat.client_id) customer_num, + application_chat_record.create_time :: DATE as "day" +FROM + application_chat_record application_chat_record + LEFT JOIN application_chat application_chat ON application_chat."id" = application_chat_record.chat_id +${default_sql} +GROUP BY "day" \ No newline at end of file diff --git a/apps/application/sql/customer_count.sql b/apps/application/sql/customer_count.sql new file mode 100644 index 000000000..0e4090890 --- /dev/null +++ b/apps/application/sql/customer_count.sql @@ -0,0 +1,5 @@ +SELECT + ( SUM ( CASE WHEN create_time :: DATE = CURRENT_DATE THEN 1 ELSE 0 END ) ) AS "customer_today_added_count", + COUNT ( "application_public_access_client"."id" ) AS "customer_added_count" +FROM + "application_public_access_client" \ No newline at end of file diff --git a/apps/application/sql/customer_count_trend.sql b/apps/application/sql/customer_count_trend.sql new file mode 100644 index 000000000..159cd031b --- /dev/null +++ b/apps/application/sql/customer_count_trend.sql @@ -0,0 +1,7 @@ +SELECT + COUNT ( "application_public_access_client"."id" ) AS "customer_added_count", + create_time :: DATE as "day" +FROM + "application_public_access_client" +${default_sql} +GROUP BY "day" \ No newline at end of file diff --git a/apps/application/swagger_api/application_statistics_api.py b/apps/application/swagger_api/application_statistics_api.py new file mode 100644 index 000000000..87fde1098 --- /dev/null +++ b/apps/application/swagger_api/application_statistics_api.py @@ -0,0 +1,86 @@ +# coding=utf-8 +""" + @project: maxkb + @Author:虎 + @file: application_statistics_api.py + @date:2024/3/27 15:09 + @desc: +""" +from drf_yasg import openapi + +from common.mixins.api_mixin import ApiMixin + + +class ApplicationStatisticsApi(ApiMixin): + @staticmethod + def get_request_params_api(): + return [openapi.Parameter(name='application_id', + in_=openapi.IN_PATH, + type=openapi.TYPE_STRING, + required=True, + description='应用id'), + openapi.Parameter(name='start_time', + in_=openapi.IN_QUERY, + type=openapi.TYPE_STRING, + required=True, + description='开始时间'), + openapi.Parameter(name='end_time', + in_=openapi.IN_QUERY, + type=openapi.TYPE_STRING, + required=True, + description='结束时间'), + ] + + class ChatRecordAggregate(ApiMixin): + @staticmethod + def get_response_body_api(): + return openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['star_num', 'trample_num', 'tokens_num', 'chat_record_count'], + properties={ + 'star_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="点赞数量", + description="点赞数量"), + + 'trample_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="点踩数量", description="点踩数量"), + 'tokens_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="token使用数量", + description="token使用数量"), + 'chat_record_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="对话次数", + description="对话次数"), + 'customer_num': openapi.Schema(type=openapi.TYPE_NUMBER, title="客户数量", + description="客户数量"), + 'customer_added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="客户新增数量", + description="客户新增数量"), + 'day': openapi.Schema(type=openapi.TYPE_STRING, + title="日期", + description="日期,只有查询趋势的时候才有该字段"), + } + ) + + class CustomerCountTrend(ApiMixin): + @staticmethod + def get_response_body_api(): + return openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['added_count'], + properties={ + 'added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="新增数量", description="新增数量"), + + 'day': openapi.Schema(type=openapi.TYPE_STRING, + title="时间", + description="时间"), + } + ) + + class CustomerCount(ApiMixin): + @staticmethod + def get_response_body_api(): + return openapi.Schema( + type=openapi.TYPE_OBJECT, + required=['added_count'], + properties={ + 'today_added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="今日新增数量", + description="今日新增数量"), + 'added_count': openapi.Schema(type=openapi.TYPE_NUMBER, title="新增数量", description="新增数量"), + + } + ) diff --git a/apps/application/urls.py b/apps/application/urls.py index 9c70def5b..648583198 100644 --- a/apps/application/urls.py +++ b/apps/application/urls.py @@ -8,6 +8,14 @@ urlpatterns = [ path('application/profile', views.Application.Profile.as_view()), path('application/embed', views.Application.Embed.as_view()), path('application/authentication', views.Application.Authentication.as_view()), + path('application//statistics/customer_count', + views.ApplicationStatistics.CustomerCount.as_view()), + path('application//statistics/customer_count_trend', + views.ApplicationStatistics.CustomerCountTrend.as_view()), + path('application//statistics/chat_record_aggregate', + views.ApplicationStatistics.ChatRecordAggregate.as_view()), + path('application//statistics/chat_record_aggregate_trend', + views.ApplicationStatistics.ChatRecordAggregateTrend.as_view()), path('application//model', views.Application.Model.as_view()), path('application//hit_test', views.Application.HitTest.as_view()), path('application//api_key', views.Application.ApplicationKey.as_view()), diff --git a/apps/application/views/application_views.py b/apps/application/views/application_views.py index d121611b3..12e47f854 100644 --- a/apps/application/views/application_views.py +++ b/apps/application/views/application_views.py @@ -14,7 +14,9 @@ from rest_framework.request import Request from rest_framework.views import APIView from application.serializers.application_serializers import ApplicationSerializer +from application.serializers.application_statistics_serializers import ApplicationStatisticsSerializer from application.swagger_api.application_api import ApplicationApi +from application.swagger_api.application_statistics_api import ApplicationStatisticsApi from common.auth import TokenAuth, has_permissions from common.constants.permission_constants import CompareConstants, PermissionConstants, Permission, Group, Operate, \ ViewPermission, RoleConstants @@ -25,6 +27,107 @@ from common.util.common import query_params_to_single_dict from dataset.serializers.dataset_serializers import DataSetSerializers +class ApplicationStatistics(APIView): + class CustomerCount(APIView): + authentication_classes = [TokenAuth] + + @action(methods=["GET"], detail=False) + @swagger_auto_schema(operation_summary="用户统计", + operation_id="用户统计", + tags=["应用/统计"], + manual_parameters=ApplicationStatisticsApi.get_request_params_api(), + responses=result.get_api_response( + ApplicationStatisticsApi.CustomerCount.get_response_body_api()) + ) + @has_permissions(ViewPermission( + [RoleConstants.ADMIN, RoleConstants.USER], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND)) + def get(self, request: Request, application_id: str): + return result.success( + ApplicationStatisticsSerializer(data={'application_id': application_id, + 'start_time': request.query_params.get( + 'start_time'), + 'end_time': request.query_params.get( + 'end_time') + }).get_customer_count()) + + class CustomerCountTrend(APIView): + authentication_classes = [TokenAuth] + + @action(methods=["GET"], detail=False) + @swagger_auto_schema(operation_summary="用户统计趋势", + operation_id="用户统计趋势", + tags=["应用/统计"], + manual_parameters=ApplicationStatisticsApi.get_request_params_api(), + responses=result.get_api_array_response( + ApplicationStatisticsApi.CustomerCountTrend.get_response_body_api())) + @has_permissions(ViewPermission( + [RoleConstants.ADMIN, RoleConstants.USER], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND)) + def get(self, request: Request, application_id: str): + return result.success( + ApplicationStatisticsSerializer(data={'application_id': application_id, + 'start_time': request.query_params.get( + 'start_time'), + 'end_time': request.query_params.get( + 'end_time') + }).get_customer_count_trend()) + + class ChatRecordAggregate(APIView): + authentication_classes = [TokenAuth] + + @action(methods=["GET"], detail=False) + @swagger_auto_schema(operation_summary="对话相关统计", + operation_id="对话相关统计", + tags=["应用/统计"], + manual_parameters=ApplicationStatisticsApi.get_request_params_api(), + responses=result.get_api_response( + ApplicationStatisticsApi.ChatRecordAggregate.get_response_body_api()) + ) + @has_permissions(ViewPermission( + [RoleConstants.ADMIN, RoleConstants.USER], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND)) + def get(self, request: Request, application_id: str): + return result.success( + ApplicationStatisticsSerializer(data={'application_id': application_id, + 'start_time': request.query_params.get( + 'start_time'), + 'end_time': request.query_params.get( + 'end_time') + }).get_chat_record_aggregate()) + + class ChatRecordAggregateTrend(APIView): + authentication_classes = [TokenAuth] + + @action(methods=["GET"], detail=False) + @swagger_auto_schema(operation_summary="对话相关统计趋势", + operation_id="对话相关统计趋势", + tags=["应用/统计"], + manual_parameters=ApplicationStatisticsApi.get_request_params_api(), + responses=result.get_api_array_response( + ApplicationStatisticsApi.ChatRecordAggregate.get_response_body_api()) + ) + @has_permissions(ViewPermission( + [RoleConstants.ADMIN, RoleConstants.USER], + [lambda r, keywords: Permission(group=Group.APPLICATION, operate=Operate.USE, + dynamic_tag=keywords.get('application_id'))], + compare=CompareConstants.AND)) + def get(self, request: Request, application_id: str): + return result.success( + ApplicationStatisticsSerializer(data={'application_id': application_id, + 'start_time': request.query_params.get( + 'start_time'), + 'end_time': request.query_params.get( + 'end_time') + }).get_chat_record_aggregate_trend()) + + class Application(APIView): authentication_classes = [TokenAuth] diff --git a/apps/common/util/field_message.py b/apps/common/util/field_message.py index fd961dc8a..3bcf067e1 100644 --- a/apps/common/util/field_message.py +++ b/apps/common/util/field_message.py @@ -86,3 +86,12 @@ class ErrMessage: 'required': gettext_lazy('【%s】此字段必填。' % field), 'null': gettext_lazy('【%s】此字段不能为null。' % field), } + + @staticmethod + def date(field: str): + return { + 'required': gettext_lazy('【%s】此字段必填。' % field), + 'null': gettext_lazy('【%s】此字段不能为null。' % field), + 'invalid': gettext_lazy('【%s】日期格式错误。请改用以下格式之一: {format}。'), + 'datetime': gettext_lazy('【%s】应为日期,但得到的是日期时间。') + } diff --git a/ui/package.json b/ui/package.json index 742ff4658..07c70038f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "axios": "^0.28.0", + "echarts": "^5.5.0", "element-plus": "^2.5.6", "install": "^0.13.0", "lodash": "^4.17.21", @@ -29,6 +30,7 @@ "md-editor-v3": "^4.12.1", "medium-zoom": "^1.1.0", "mitt": "^3.0.0", + "moment": "^2.30.1", "npm": "^10.2.4", "nprogress": "^0.2.0", "pinia": "^2.1.6", diff --git a/ui/src/api/application-overview.ts b/ui/src/api/application-overview.ts index 940088353..d77da6322 100644 --- a/ui/src/api/application-overview.ts +++ b/ui/src/api/application-overview.ts @@ -55,9 +55,22 @@ const putAPIKey: ( return put(`${prefix}/${applicaiton_id}/api_key/${api_key_id}`, data, undefined, loading) } +/** + * 统计 + * @param 参数 applicaiton_id, data + */ +const getStatistics: ( + applicaiton_id: string, + data: any, + loading?: Ref +) => Promise> = (applicaiton_id, data, loading) => { + return get(`${prefix}/${applicaiton_id}/statistics/chat_record_aggregate_trend`, data, loading) +} + export default { getAPIKey, postAPIKey, delAPIKey, - putAPIKey + putAPIKey, + getStatistics } diff --git a/ui/src/components/app-charts/components/LineCharts.vue b/ui/src/components/app-charts/components/LineCharts.vue new file mode 100644 index 000000000..5c236e2cb --- /dev/null +++ b/ui/src/components/app-charts/components/LineCharts.vue @@ -0,0 +1,128 @@ + + + diff --git a/ui/src/components/app-charts/index.vue b/ui/src/components/app-charts/index.vue new file mode 100644 index 000000000..bc3f845a5 --- /dev/null +++ b/ui/src/components/app-charts/index.vue @@ -0,0 +1,30 @@ + + diff --git a/ui/src/components/icons/index.ts b/ui/src/components/icons/index.ts index 775c734c2..b093c63bc 100644 --- a/ui/src/components/icons/index.ts +++ b/ui/src/components/icons/index.ts @@ -640,5 +640,93 @@ export const iconMap: any = { ) ]) } + }, + 'app-user': { + iconReader: () => { + return h('i', [ + h( + 'svg', + { + style: { height: '100%', width: '100%' }, + viewBox: '0 0 24 24', + version: '1.1', + xmlns: 'http://www.w3.org/2000/svg' + }, + [ + h('path', { + d: 'M15 13H9C6.23858 13 3 14.9314 3 18.4V21.1C3 21.597 3.44772 22 4 22H20C20.5523 22 21 21.597 21 21.1V18.4C21 14.9285 17.7614 13 15 13Z', + fill: 'currentColor' + }), + h('path', { + d: 'M7 6.99997C7 9.76139 9.23858 12 12 12C14.7614 12 17 9.76139 17 6.99997C17 4.23855 14.7614 1.99997 12 1.99997C9.23858 1.99997 7 4.23855 7 6.99997Z', + fill: 'currentColor' + }) + ] + ) + ]) + } + }, + 'app-question': { + iconReader: () => { + return h('i', [ + h( + 'svg', + { + style: { height: '100%', width: '100%' }, + viewBox: '0 0 24 24', + version: '1.1', + xmlns: 'http://www.w3.org/2000/svg' + }, + [ + h('path', { + d: 'M12.7071 22.2009L17 18.5111H21.5C22.0523 18.5111 22.5 18.0539 22.5 17.4899V2.52112C22.5 1.95715 22.0523 1.49997 21.5 1.49997H2C1.44772 1.49997 1 1.95715 1 2.52112V17.4899C1 18.0539 1.44772 18.5111 2 18.5111H7L11.2929 22.2009C11.6834 22.5997 12.3166 22.5997 12.7071 22.2009ZM6.5 8.49997H7.5C8.05228 8.49997 8.5 8.94768 8.5 9.49997V10.5C8.5 11.0523 8.05228 11.5 7.5 11.5H6.5C5.94772 11.5 5.5 11.0523 5.5 10.5V9.49997C5.5 8.94768 5.94772 8.49997 6.5 8.49997ZM10.5 9.49997C10.5 8.94768 10.9477 8.49997 11.5 8.49997H12.5C13.0523 8.49997 13.5 8.94768 13.5 9.49997V10.5C13.5 11.0523 13.0523 11.5 12.5 11.5H11.5C10.9477 11.5 10.5 11.0523 10.5 10.5V9.49997ZM16.5 8.49997H17.5C18.0523 8.49997 18.5 8.94768 18.5 9.49997V10.5C18.5 11.0523 18.0523 11.5 17.5 11.5H16.5C15.9477 11.5 15.5 11.0523 15.5 10.5V9.49997C15.5 8.94768 15.9477 8.49997 16.5 8.49997Z', + fill: 'currentColor' + }) + ] + ) + ]) + } + }, + 'app-tokens': { + iconReader: () => { + return h('i', [ + h( + 'svg', + { + style: { height: '100%', width: '100%' }, + viewBox: '0 0 24 24', + version: '1.1', + xmlns: 'http://www.w3.org/2000/svg' + }, + [ + h('path', { + d: 'M15.6 2.39996C12.288 2.39996 9.60002 5.08796 9.60002 8.39996C9.60002 9.11996 9.74402 9.79196 9.97202 10.428L2.47325 17.9267C2.42636 17.9736 2.40002 18.0372 2.40002 18.1035V21.1C2.40002 21.3761 2.62388 21.6 2.90002 21.6H4.30002C4.57617 21.6 4.80002 21.3761 4.80002 21.1V20.4H6.70003C6.97617 20.4 7.20002 20.1761 7.20002 19.9V18H8.40002L10.8 15.6H12L13.572 14.028C14.208 14.256 14.88 14.4 15.6 14.4C18.912 14.4 21.6 11.712 21.6 8.39996C21.6 5.08796 18.912 2.39996 15.6 2.39996ZM17.4 8.39996C16.404 8.39996 15.6 7.59596 15.6 6.59996C15.6 5.60396 16.404 4.79996 17.4 4.79996C18.396 4.79996 19.2 5.60396 19.2 6.59996C19.2 7.59596 18.396 8.39996 17.4 8.39996Z', + fill: 'currentColor' + }) + ] + ) + ]) + } + }, + 'app-user-stars': { + iconReader: () => { + return h('i', [ + h( + 'svg', + { + style: { height: '100%', width: '100%' }, + viewBox: '0 0 24 24', + version: '1.1', + xmlns: 'http://www.w3.org/2000/svg' + }, + [ + h('path', { + d: 'M12 23C18.0751 23 23 18.0751 23 12C23 5.92484 18.0751 0.999969 12 0.999969C5.92487 0.999969 1 5.92484 1 12C1 18.0751 5.92487 23 12 23ZM8.5 10.5C7.67157 10.5 7 9.8284 7 8.99997C7 8.17154 7.67157 7.49997 8.5 7.49997C9.32843 7.49997 10 8.17154 10 8.99997C10 9.8284 9.32843 10.5 8.5 10.5ZM17 8.99997C17 9.8284 16.3284 10.5 15.5 10.5C14.6716 10.5 14 9.8284 14 8.99997C14 8.17154 14.6716 7.49997 15.5 7.49997C16.3284 7.49997 17 8.17154 17 8.99997ZM16.9779 13.4994C16.7521 16.0264 14.8169 18 12 18C9.18312 18 7.24789 16.0264 7.02213 13.4994C6.99756 13.2244 7.22386 13 7.5 13H16.5C16.7761 13 17.0024 13.2244 16.9779 13.4994Z', + fill: 'currentColor' + }) + ] + ) + ]) + } } } diff --git a/ui/src/styles/app.scss b/ui/src/styles/app.scss index b47dd0ff8..0d49981d9 100644 --- a/ui/src/styles/app.scss +++ b/ui/src/styles/app.scss @@ -105,6 +105,9 @@ h4 { .h-full { height: 100%; } +.w-120 { + width: 120px; +} .w-240 { width: 240px; } @@ -206,6 +209,10 @@ h4 { align-items: center; } +.align-baseline { + align-items: baseline; +} + .text-left { text-align: left; } @@ -424,9 +431,13 @@ h4 { color: var(--el-color-info); } +.color-secondary { + color: var(--app-text-color-secondary); +} + .app-warning-icon { font-size: 16px; - color: #646a73; + color: var(--app-text-color-secondary); } .dotting { diff --git a/ui/src/utils/time.ts b/ui/src/utils/time.ts index 1a0ebc18a..82cd16211 100644 --- a/ui/src/utils/time.ts +++ b/ui/src/utils/time.ts @@ -1,3 +1,13 @@ +import moment from 'moment' + +// 当天日期 YYYY-MM-DD +export const nowDate = moment().format('YYYY-MM-DD') + +// 当前时间的前n天 +export function beforeDay(n) { + return moment().subtract(n, 'days').format('YYYY-MM-DD') +} + const getCheckDate = (timestamp: any) => { if (!timestamp) return false const dt = new Date(timestamp) diff --git a/ui/src/utils/utils.ts b/ui/src/utils/utils.ts index 551c88400..062d6c625 100644 --- a/ui/src/utils/utils.ts +++ b/ui/src/utils/utils.ts @@ -64,3 +64,15 @@ export function isAllPropertiesEmpty(obj: object) { value === null || typeof value === 'undefined' || (typeof value === 'string' && !value) ) } + +// 数组对象中某一属性值的集合 +export function getAttrsArray(array, attr) { + return array.map((item) => { + return item[attr] + }) +} + +// 求和 +export function getSum(array) { + return array.reduce((totol, item) => totol + item, 0) +} diff --git a/ui/src/views/applicaiton-overview/component/StatisticsCharts.vue b/ui/src/views/applicaiton-overview/component/StatisticsCharts.vue new file mode 100644 index 000000000..e0fd889d7 --- /dev/null +++ b/ui/src/views/applicaiton-overview/component/StatisticsCharts.vue @@ -0,0 +1,161 @@ + + + diff --git a/ui/src/views/applicaiton-overview/index.vue b/ui/src/views/applicaiton-overview/index.vue index dba1cce5e..9e000a857 100644 --- a/ui/src/views/applicaiton-overview/index.vue +++ b/ui/src/views/applicaiton-overview/index.vue @@ -1,78 +1,104 @@